Подготовка последовательностей строк с использованием pandas

#pandas

#pandas

Вопрос:

У меня есть этот набор данных транзакций:

  ------ -------- ------- ------- 
| name | amount | feat1 | feat2 |
 ------ -------- ------- ------- 
| dave | 24     | -     | -     |
 ------ -------- ------- ------- 
| john | 55     | -     | -     |
 ------ -------- ------- ------- 
| dave | 56     | -     | -     |
 ------ -------- ------- ------- 
| dave | 94     | -     | -     |
 ------ -------- ------- ------- 
| dave | 10     | -     | -     |
 ------ -------- ------- ------- 
| john | 3      | -     | -     |
 ------ -------- ------- ------- 
| dave | 7      | -     | -     |
 ------ -------- ------- ------- 
| john | 77     | -     | -     |
 ------ -------- ------- ------- 
| john | 88     | -     | -     |
 ------ -------- ------- ------- 
| dave | 34     | -     | -     |
 ------ -------- ------- ------- 
| john | 84     | -     | -     |
 ------ -------- ------- ------- 
| john | 22     | -     | -     |
 ------ -------- ------- ------- 
 

Я хочу создать последовательности, которые будут выглядеть так:

window_size прошлые транзакции -> следующая транзакция, но для каждого клиента. Это означает, что window_size = 4 я хочу, чтобы результат выглядел примерно так:

 source: 

 ------ -------- ------- ------- 
| name | amount | feat1 | feat2 |
 ------ -------- ------- ------- 
| dave | 24     | -     | -     |                  next:
 ------ -------- ------- -------                
| dave | 56     | -     | -     |       ------ -------- ------- ------- 
 ------ -------- ------- -------       | dave | 7      | -     | -     |
| dave | 94     | -     | -     |       ------ -------- ------- ------- 
 ------ -------- ------- ------- 
| dave | 10     | -     | -     |
 ------ -------- ------- ------- 

source:                                       

 ------ -------- ------- ------- 
| name | amount | feat1 | feat2 |
 ------ -------- ------- -------                next:
| dave | 56     | -     | -     |            
 ------ -------- ------- -------       ------ -------- ------- ------- 
| dave | 94     | -     | -     |     | dave | 34     | -     | -     |
 ------ -------- ------- -------       ------ -------- ------- ------- 
| dave | 10     | -     | -     |   
 ------ -------- ------- -------    
| dave | 7      | -     | -     |
 ------ -------- ------- ------- 


source:
 ------ -------- ------- ------- 
| name | amount | feat1 | feat2 |
 ------ -------- ------- -------                  next:
| john | 55     | -     | -     |                
 ------ -------- ------- -------        ------ -------- ------- -------             
| john | 3      | -     | -     |      | john | 84     | -     | -     |
 ------ -------- ------- -------        ------ -------- ------- ------- 
| john | 77     | -     | -     |      
 ------ -------- ------- -------       
| john | 88     | -     | -     |
 ------ -------- ------- ------- 

 source:
 ------ -------- ------- ------- 
| name | amount | feat1 | feat2 |
 ------ -------- ------- -------                   next:
| john | 3      | -     | -     |
 ------ -------- ------- -------          ------ -------- ------- ------- 
| john | 77     | -     | -     |        | john | 22     | -     | -     |
 ------ -------- ------- -------          ------ -------- ------- ------- 
| john | 88     | -     | -     |
 ------ -------- ------- ------- 
| john | 84     | -     | -     |
 ------ -------- ------- ------- 
 

и то же самое для другого пользователя ‘john’ и вообще для каждого пользователя.
Я хочу, чтобы мои выходные данные представляли собой две переменные python: sequences and targets
sequences будут иметь форму (window_size, num_features) и tragets будут иметь форму (1, num_features)

Комментарии:

1. Какую операцию вы хотели бы выполнить за прошлые транзакции? Результатом должно быть mean значение, min или что-то другое?

2. просто сложите их вместе и сохраните как фрейм данных

3. Но как вы получаете target данные из прошлых транзакций?

4. просто в следующей транзакции я отредактировал свой вопрос, чтобы спроецировать это

Ответ №1:

Учитывая, что ваш фрейм данных вызывается df , вы можете попробовать следующий код:

 window_size = 4
my_transactions = {}

for name in df['name'].unique():
    _user_transactions = df[df['name'] == name]

    my_transactions[name] = [
        {
            'past': _user_transactions.iloc[i:i window_size],
            'target': _user_transactions.iloc[i window_size]
        }
        for i in range(len(_user_transactions) - window_size)
    ]
 

Это вернет a dictionary с ключами, равными именам пользователей.

Для каждого пользователя будет список словарей, каждый из которых содержит ключи past и target , содержащий соответствующие фреймы данных. Смотрите пример ниже

 for name in my_transactions:
    print('User:', name)
    for transaction in my_transactions[name]:
        print('Past transactions:')
        print(transaction['past'])
        print('Target transaction:')
        print(transaction['target'])
        print()
    print()
 

Для пользователя dave вывод:

 User: dave
Past transactions:
   name  amount
0  dave      24
2  dave      56
3  dave      94
4  dave      10
Target transaction:
name      dave
amount       7
Name: 6, dtype: object

Past transactions:
   name  amount
2  dave      56
3  dave      94
4  dave      10
6  dave       7
Target transaction:
name      dave
amount      34
Name: 9, dtype: object
 

И для john

 User: john
Past transactions:
   name  amount
1  john      55
5  john       3
7  john      77
8  john      88
Target transaction:
name      john
amount      84
Name: 10, dtype: object

Past transactions:
    name  amount
5   john       3
7   john      77
8   john      88
10  john      84
Target transaction:
name      john
amount      22
Name: 11, dtype: object
 

Сообщите нам, если у вас возникнут какие-либо дополнительные проблемы.

Комментарии:

1. Спасибо за ваше решение, оно действительно работает. Мне было интересно, масштабируется ли он для наборов данных с миллионами записей. Как вы думаете, это можно реализовать более эффективным способом? например, используя pandas’ groupby и ? rolling

Ответ №2:

 import pandas as pd
from tabulate import tabulate


def extract_window_and_target(df, i, window_size):
    # get the data with same user and with index bigger than i
    user_name = df.iloc[i, :][0]
    user_events_from_i = df.iloc[i:, :].loc[df["name"] == user_name, :]
    # extract window and target
    X = user_events_from_i.iloc[:window_size, :].reset_index(drop=True)
    y = user_events_from_i.iloc[window_size:window_size   1, :].reset_index(drop=True)
    has_empty_target = True if len(y) == 0 else False
    return X, y, has_empty_target


columns = ["name", "amount"]
data = [["dave", "24"],
        ["john", "55"],
        ["dave", "56"],
        ["dave", "94"],
        ["dave", "10"],
        ["john", "3"],
        ["dave", "7"],
        ["john", "77"],
        ["john", "88"],
        ["dave", "34"],
        ["john", "84"],
        ["john", "22"]]
df = pd.DataFrame(data, columns=columns)

window_size = 4
for i in range(len(df)):
    X, y, has_empty_target = extract_window_and_target(df, i, window_size)
    if not has_empty_target:
        print("window (training data) :")
        print(tabulate(X, headers=X.columns, tablefmt="psql"))
        print("target:")
        print(tabulate(y, headers=y.columns, tablefmt="psql"))
 

Вывод:

 window 0 (training data) :
 ---- -------- ---------- 
|    | name   |   amount |
|---- -------- ----------|
|  0 | dave   |       24 |
|  1 | dave   |       56 |
|  2 | dave   |       94 |
|  3 | dave   |       10 |
 ---- -------- ---------- 
target 0:
 ---- -------- ---------- 
|    | name   |   amount |
|---- -------- ----------|
|  0 | dave   |        7 |
 ---- -------- ---------- 
window 1 (training data) :
 ---- -------- ---------- 
|    | name   |   amount |
|---- -------- ----------|
|  0 | john   |       55 |
|  1 | john   |        3 |
|  2 | john   |       77 |
|  3 | john   |       88 |
 ---- -------- ---------- 
target 1:
 ---- -------- ---------- 
|    | name   |   amount |
|---- -------- ----------|
|  0 | john   |       84 |
 ---- -------- ---------- 
window 2 (training data) :
 ---- -------- ---------- 
|    | name   |   amount |
|---- -------- ----------|
|  0 | dave   |       56 |
|  1 | dave   |       94 |
|  2 | dave   |       10 |
|  3 | dave   |        7 |
 ---- -------- ---------- 
target 2:
 ---- -------- ---------- 
|    | name   |   amount |
|---- -------- ----------|
|  0 | dave   |       34 |
 ---- -------- ---------- 
window 5 (training data) :
 ---- -------- ---------- 
|    | name   |   amount |
|---- -------- ----------|
|  0 | john   |        3 |
|  1 | john   |       77 |
|  2 | john   |       88 |
|  3 | john   |       84 |
 ---- -------- ---------- 
target 5:
 ---- -------- ---------- 
|    | name   |   amount |
|---- -------- ----------|
|  0 | john   |       22 |
 ---- -------- ---------- 
 

Это также будет работать, если у вас есть несколько функций в Dataframe.
(.. однако это не оптимальное решение для скорости)

Комментарии:

1. Спасибо за ваше решение. После того, как я был осужден, это действительно работает, мне интересно, какое это лучшее решение с точки зрения скорости. Может быть, используя pandas groupby и rolling функции?

2. да, прокатка и применение extract_window_and_target повысят скорость, потому что вы можете избежать фильтрации всего набора данных на каждой итерации. однако, если вы принимаете факт потери хронологического порядка транзакции, решение Ralobrusto должно быть быстрее (он фильтрует только один раз для каждого имени).