Перегруппировка фрейма данных в pandas с использованием ключа. Быстрее, чем перебирать строки? - PullRequest
1 голос
/ 17 января 2020

Сводка:

Я хочу расположить временные ряды кодов (большой набор данных), которые представляют начало и конец действий, в диаграмме Ганта, поэтому мне нужно перегруппировать их в Задачу (имя ), Столбцы Start (время) и Fini sh (время). Тем не менее, я могу пока делать это очень медленно, перебирая по каждой строке букву a для l oop :(

(я пробовал groupby и pivot, но я просто не * gr asp это хорошо пока достаточно, чтобы заставить их делать то, что я хочу.)

Ключ

У меня есть словарь 'key' / df с start_code , end_code и действие метка . Упрощенный пример:

import pandas as pd
code_key_cols = ["start_code", "end_code", "label"]
code_key = [[1, 2, "a"],
            [3, 4, "b"],
            [5, 6, "c"],
            [7, 8, "d"]]
code_df = pd.DataFrame(code_key, columns=code_key_cols)

Out[]:    start_code  end_code label
      0           1         2     a
      1           3         4     b
      2           5         6     c
      3           7         8     d

Данные

Тогда у меня есть набор данных, который представляет собой просто временной ряд, когда эти коды. Я хочу организовать их таким образом, чтобы построить график Ганта. Для графика это означает наличие столбца задача , начало , fini sh.

( Просто создаем поддельные данные здесь, например, имитируя поведение реальных данных, когда один и тот же тип действия не может происходить дважды параллельно, только одновременно)

from random import shuffle
data = []
for i in range(3000):
    start_codes = [x for x in code_df.iloc[:, 0]]
    end_codes = [x for x in code_df.iloc[:, 1]]
    shuffle(start_codes)
    shuffle(end_codes)
    [data.append(x) for x in start_codes]
    [data.append(x) for x in end_codes]

data_cols = ["code", "time"]
data_df = pd.DataFrame()
data_df['code'] = data
data_df['time'] = pd.date_range(start="19700101", periods=len(data))

print(data_df.head())
   code       time
0     3 1970-01-01
1     1 1970-01-02
2     7 1970-01-03
3     5 1970-01-04
4     2 1970-01-05

Моя попытка:

Я могу сделать это, но только очень медленно, итерация строка за строкой! Я уверен, что pandas имеет более эффективный способ сделать это. Как бы вы это сделали? Вот как я это сделал, но с df 12K строк это занимает более 13 с: (

import numpy as np
lst = []
for _, code_row in code_df.iterrows():
    begin = True
    task = np.nan
    start = np.nan
    finish = np.nan
    for _, data_row in data_df.iterrows():
        if begin:
            if code_row['start_code'] == data_row['code']:
                task = code_row.label
                start = data_row.time
                begin = False
        else:
            if code_row['end_code'] == data_row['code']:
                finish = data_row.time
                begin = True
                lst.append([task, start, finish])

df3 = pd.DataFrame(data=lst, columns=["Task", 'Start', 'Finish'])

Output

Для контекста я покажу цель, построив диаграмму Ганта с следующий код ( изменение для i в диапазоне выше с 3000 на 10 для упрощения).

import plotly.figure_factory as ff
import plotly.io as pio
pio.renderers.default = "browser"

fig = ff.create_gantt(df3, group_tasks=True)
fig.show()

Gantt chart example with 10 iterations кстати, если вы прочитали это далеко, спасибо очень за ваше время! :) 1048 *

1 Ответ

2 голосов
/ 18 января 2020

Надеюсь, это поможет. Это должно дать вам тот же результат:

# we'll create a new dataframe out of two slices on data_df (resulting in two new dataframes), namely those rows belonging to start_code and those belonging to end_code.
# next, sort the slices on code and time such that our slices match in order (this builds on the concurrent assumption you stated)
# drop unwanted columns and rename others as desired
# reset indices as otherwise pd.concat tries to adhere to the old indices
# merge the labels from code_df

df3_new = pd.concat([
    data_df[data_df.code.isin(code_df.start_code)]
        .sort_values(['code', 'time'])
        .reset_index(drop=True)
        .rename(columns={'time': 'Start'}),
    data_df[data_df.code.isin(code_df.end_code)]
        .sort_values(['code', 'time'])
        .reset_index(drop=True)
        .rename(columns={'time': 'Finish'})
        .drop('code', axis=1)
], axis=1) \
    .merge(code_df, how='left', left_on='code', right_on='start_code') \
    .drop(['code', 'start_code', 'end_code'], axis=1) \
    .rename(columns={'label': 'Task'})

# which yields the same outcome (for the given set at least)
df3.equals(df3_new.loc[:, ['Task','Start', 'Finish']])
True

Со следующей средней производительностью в данном наборе:

12.5 ms ± 435 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
...