Как эффективно расширить датафрейм pandas с группами - PullRequest
0 голосов
/ 13 мая 2019

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

Данные выглядят так:

import pandas as pd
import datetime as dt
import numpy as np

df = pd.DataFrame()
df['start'] = [dt.datetime(2017, 4, 3,5,22,21), dt.datetime(2017, 4, 5,3,51,22),\
               dt.datetime(2017, 4, 4,4,23,33),dt.datetime(2017, 4, 3,7,28,45),\
               dt.datetime(2017, 4, 6,5,22,24),dt.datetime(2017, 4, 6,5,22,56)]

df['end'] = [dt.datetime(2017, 4, 3,6,33,23), dt.datetime(2017, 4,5,3,52,46),\
             dt.datetime(2017, 4,4,4,58,12),dt.datetime(2017, 4, 4,1,23,34),\
            dt.datetime(2017, 4, 7,5,22,24),dt.datetime(2017, 4, 7,5,22,47)]
df['group'] = ['1', '2', '3','1','2','3']
df['value'] = ['a', 'b', 'c','b','c','a']

start   end group   value
0   2017-04-03 05:22:21 2017-04-03 06:33:23 1   a
1   2017-04-05 03:51:22 2017-04-05 03:52:46 2   b
2   2017-04-04 04:23:33 2017-04-04 04:58:12 3   c
3   2017-04-03 07:28:45 2017-04-04 01:23:34 1   b
4   2017-04-06 05:22:24 2017-04-03 05:22:24 2   c
5   2017-04-03 05:22:56 2017-04-03 05:22:47 3   a

Я пробовал следующий метод:

  1. Построение нового кадра данных с индексом в диапазоне от самого раннего начала и самого последнего конца.

  2. Группировка по group_ID

  3. Итерация по строкам группы, создание из каждой строки небольшого кадра данных с индексом в дате начала и дате окончания строки, в которой хранится значение строки

4.Конкатенация небольших кадров данных из той же группыв один кадр данных

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

Вот фрагмент кода:


def turn_deltas(row,col):
    key = str(row['group'])
    df = pd.DataFrame(index=pd.date_range(row['start'], row['end'], freq="1S"))
    df[key] = row[col]
    return df

grouped = df.groupby("group")
data = pd.DataFrame(index=pd.date_range(df['start'].min(), df['end'].max(), freq="1s")) 
for name, group in (grouped):
    for i, row in enumerate(group.iterrows()):
        if i == 0:
            df_2 = turn_deltas(row[1],"value")
        else:
            df_2 = pd.concat([df_2, turn_deltas(row[1],"value")], axis=0)
    data = data.merge(df_2, how="left", left_index=True, right_index=True)

print (data)

Мой код работает, но выполняют задачу очень (очень) медленно

Наконец, я получил этот обновленный фрейм данных:

2017-04-03 05:22:21    a  NaN  NaN
2017-04-03 05:22:22    a  NaN  NaN
2017-04-03 05:22:23    a  NaN  NaN
2017-04-03 05:22:24    a  NaN  NaN
2017-04-03 05:22:25    a  NaN  NaN
2017-04-03 05:22:26    a  NaN  NaN
2017-04-03 05:22:27    a  NaN  NaN
2017-04-03 05:22:28    a  NaN  NaN
2017-04-03 05:22:29    a  NaN  NaN
2017-04-03 05:22:30    a  NaN  NaN
2017-04-03 05:22:31    a  NaN  NaN
2017-04-03 05:22:32    a  NaN  NaN
2017-04-03 05:22:33    a  NaN  NaN
2017-04-03 05:22:34    a  NaN  NaN
2017-04-03 05:22:35    a  NaN  NaN
2017-04-03 05:22:36    a  NaN  NaN
2017-04-03 05:22:37    a  NaN  NaN
2017-04-03 05:22:38    a  NaN  NaN
2017-04-03 05:22:39    a  NaN  NaN
2017-04-03 05:22:40    a  NaN  NaN
2017-04-03 05:22:41    a  NaN  NaN
2017-04-03 05:22:42    a  NaN  NaN
2017-04-03 05:22:43    a  NaN  NaN
2017-04-03 05:22:44    a  NaN  NaN
2017-04-03 05:22:45    a  NaN  NaN
2017-04-03 05:22:46    a  NaN  NaN
2017-04-03 05:22:47    a  NaN  NaN
2017-04-03 05:22:48    a  NaN  NaN
2017-04-03 05:22:49    a  NaN  NaN
2017-04-03 05:22:50    a  NaN  NaN
...                  ...  ...  ...
2017-04-07 05:22:18  NaN    c    a
2017-04-07 05:22:19  NaN    c    a
2017-04-07 05:22:20  NaN    c    a
2017-04-07 05:22:21  NaN    c    a
2017-04-07 05:22:22  NaN    c    a
2017-04-07 05:22:23  NaN    c    a
2017-04-07 05:22:24  NaN    c    a
2017-04-07 05:22:25  NaN  NaN    a
2017-04-07 05:22:26  NaN  NaN    a
2017-04-07 05:22:27  NaN  NaN    a
2017-04-07 05:22:28  NaN  NaN    a
2017-04-07 05:22:29  NaN  NaN    a
2017-04-07 05:22:30  NaN  NaN    a
2017-04-07 05:22:31  NaN  NaN    a
2017-04-07 05:22:32  NaN  NaN    a
2017-04-07 05:22:33  NaN  NaN    a
2017-04-07 05:22:34  NaN  NaN    a
2017-04-07 05:22:35  NaN  NaN    a
2017-04-07 05:22:36  NaN  NaN    a
2017-04-07 05:22:37  NaN  NaN    a
2017-04-07 05:22:38  NaN  NaN    a
2017-04-07 05:22:39  NaN  NaN    a
2017-04-07 05:22:40  NaN  NaN    a
2017-04-07 05:22:41  NaN  NaN    a
2017-04-07 05:22:42  NaN  NaN    a
2017-04-07 05:22:43  NaN  NaN    a
2017-04-07 05:22:44  NaN  NaN    a
2017-04-07 05:22:45  NaN  NaN    a
2017-04-07 05:22:46  NaN  NaN    a
2017-04-07 05:22:47  NaN  NaN    a

Примечание : Этот код является частью всего проекта.После того, как я выполню это преобразование, я также использую get_dummies(), чтобы получить отдельный столбец для каждого значения каждого столбца, чтобы вы могли использовать его и в своей стратегии реализации

Спасибо!

Ответы [ 2 ]

0 голосов
/ 17 мая 2019

Во-первых, вы должны действительно преобразовать значение в некоторый тип dtype, отличный от объекта, т.е. использовать 0,1,2 вместо 'a', 'b', 'c'.

Что касается кода преобразования,это кажется очень быстрым, по крайней мере, на вашем примере df.и довольно короткий и хорошо читаемый.

data = pd.DataFrame(index=pd.date_range(df['start'].min(), df['end'].max(), freq="1S"))

for i,row in df.iterrows():
    data.loc[(data.index >= row['start'])&(data.index<=row['end']),
             row['group']] = row['value']    
0 голосов
/ 13 мая 2019

Я бы использовал merge_ordered для построения фрейма данных для каждой группы, индексированной индексом вашего data фрейма данных.Это будет иметь нежелательные значения, поэтому они должны быть очищены.Но с тех пор легко построить ваш окончательный фрейм данных:

for g, dg in df.groupby('group'):
    # build a dataframe per group with the final index
    dy = pd.merge_ordered(data.rename_axis('dat').reset_index(), dg,
         left_on='dat', right_on='start', fill_method='ffill')
    # clean values outside of [start:end] range
    dy.loc[(dy.start>dy.dat)|(dy.dat>dy.end), 'group'] = np.nan
    dy.loc[(dy.start>dy.dat)|(dy.dat>dy.end), 'value'] = np.nan
    # and use that to set the column in the final dataframe
    data[g] = dy.set_index('dat').value

Если производительность действительно имеет значение, правильное использование индекса имеет значение.Эта версия должна быть примерно в 3 раза быстрее:

for g, dg in df.groupby('group'):
    # build a dataframe per group with the final index
    dy = pd.merge_asof(data, dg.set_index('start'),
                 left_index=True, right_index=True)
    # clean values outside of [start:end] range
    dy.loc[dy.index>dy.end,'value'] = np.nan
    # and use that to set the column in the final dataframe
    data[g] = dy.value
...