Векторизация в Пандах - PullRequest
0 голосов
/ 21 мая 2018

У меня есть эта операция на большом DataFrame Pandas, и, конечно, она очень медленная.

def get_last_status_in_range(df, created_dt, created_id, window_size=15, gap_size=5):
    since = created_dt - timedelta(days=(window_size + gap_size))
    until = created_dt - timedelta(days=gap_size)
    try:
        status = df[(df.created_dt >= since) & (df.created_dt < until) &
                    (df.number_id == created_id)]['status'].iloc[-1]
    except IndexError:
        # Not found
        status = None
    return status

idx = 0
last_status_in_range = np.array([None] * len(df), dtype=str)
for row in df.itertuples():
    created_dt = row.created_dt
    created_id = row.number_id
    last_status_in_range[idx] = get_last_status_in_range(df, created_dt, created_id)
    idx += 1

Моя цель - получить DF со столбцами «create_dt», «number_id» и «status»", получить для каждой строки последний" статус "для того же самого" number_id ", но в указанном диапазоне дат в прошлом.

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

Как я могу векторизовать операцию, используя некоторые значения в одном и том же DataFrame?

EDIT:

Учитывая следующее DF:

In [120]: df
Out[120]: 
   number_id                 created_dt status
20     BBB 2018-05-18 20:28:51.388001      u
12     BBB 2018-05-19 12:28:51.388001      u
2      CCC 2018-05-19 23:28:51.388001      u
27     CCC 2018-05-20 22:28:51.388001      a
1      CCC 2018-05-21 05:28:51.388001      u
14     BBB 2018-05-21 12:28:51.388001      r
17     AAA 2018-05-24 21:28:51.388001      a
28     CCC 2018-05-30 16:28:51.388001      a
0      AAA 2018-05-31 23:28:51.388001      r
24     CCC 2018-06-01 00:28:51.388001      r
4      BBB 2018-06-01 11:28:51.388001      r
23     BBB 2018-06-01 19:28:51.388001      r
6      AAA 2018-06-03 14:28:51.388001      a
3      CCC 2018-06-04 15:28:51.388001      u
19     AAA 2018-06-05 06:28:51.388001      u
5      AAA 2018-06-05 20:28:51.388001      r
21     AAA 2018-06-06 04:28:51.388001      a
9      BBB 2018-06-06 18:28:51.388001      r
25     AAA 2018-06-07 15:28:51.388001      r
11     BBB 2018-06-08 09:28:51.388001      r
10     BBB 2018-06-08 21:28:51.388001      u
13     BBB 2018-06-09 04:28:51.388001      a
7      AAA 2018-06-09 16:28:51.388001      r
22     AAA 2018-06-12 07:28:51.388001      r
26     BBB 2018-06-13 03:28:51.388001      u
15     AAA 2018-06-14 08:28:51.388001      a
8      CCC 2018-06-14 14:28:51.388001      r
18     CCC 2018-06-15 17:28:51.388001      u
16     BBB 2018-06-16 02:28:51.388001      a
29     AAA 2018-06-16 08:28:51.388001      r
30     AAA 2018-06-17 02:28:51.388001      a

Я ожидаю, что результат будет:

In [124]: df
Out[124]: 
   number_id                 created_dt status prev_status
20     BBB 2018-05-18 20:28:51.388001      u        None
12     BBB 2018-05-19 12:28:51.388001      u        None
2      CCC 2018-05-19 23:28:51.388001      u        None
27     CCC 2018-05-20 22:28:51.388001      a        None
1      CCC 2018-05-21 05:28:51.388001      u        None
14     BBB 2018-05-21 12:28:51.388001      r        None
17     AAA 2018-05-24 21:28:51.388001      a        None
28     CCC 2018-05-30 16:28:51.388001      a           u
0      AAA 2018-05-31 23:28:51.388001      r           a
24     CCC 2018-06-01 00:28:51.388001      r           u
4      BBB 2018-06-01 11:28:51.388001      r           r
23     BBB 2018-06-01 19:28:51.388001      r           r
6      AAA 2018-06-03 14:28:51.388001      a           a
3      CCC 2018-06-04 15:28:51.388001      u           u
19     AAA 2018-06-05 06:28:51.388001      u           a
5      AAA 2018-06-05 20:28:51.388001      r           a
21     AAA 2018-06-06 04:28:51.388001      a           r
9      BBB 2018-06-06 18:28:51.388001      r           r
25     AAA 2018-06-07 15:28:51.388001      r           r
11     BBB 2018-06-08 09:28:51.388001      r           r
10     BBB 2018-06-08 21:28:51.388001      u           r
13     BBB 2018-06-09 04:28:51.388001      a           r
7      AAA 2018-06-09 16:28:51.388001      r           a
22     AAA 2018-06-12 07:28:51.388001      r           a
26     BBB 2018-06-13 03:28:51.388001      u           r
15     AAA 2018-06-14 08:28:51.388001      a           r
8      CCC 2018-06-14 14:28:51.388001      r           u
18     CCC 2018-06-15 17:28:51.388001      u           u
16     BBB 2018-06-16 02:28:51.388001      a           a
29     AAA 2018-06-16 08:28:51.388001      r           r
30     AAA 2018-06-17 02:28:51.388001      a           r

Как видите, значение в столбце "prev_status" равното же значение для предыдущей строки, которое совпадает с тем же «number_id» (где предыдущая строка находится после применения условий даты к столбцу «create_dt»)

1 Ответ

0 голосов
/ 21 мая 2018

этот метод использует реляционную алгебру для ускорения операции, а не векторизации

с использованием pandas.merge_asof, мы можем объединить два кадра данных, выбирая последнюю строку из 2-го кадра, где выполняется сравнениеполе меньше, чем поле сравнения 1-го кадра.

Создайте столбец с именем until.Это временный столбец, который мы отбросим позже

df['until'] = df.created_dt - pd.Timedelta(days=5)

Объединить df с самим собой, используя & then_dt, то есть последнюю строку, такую, что created_dt right-df предшествует until и * left-df number_id одинаково для обоих dfs

merged = pd.merge_asof(df, df, left_on='until', right_on='created_dt', by='number_id', suffixes=('', '_y'), allow_exact_matches=False)

Установите status_y в np.nan, где created_dt_y до created_dt - 20 days

merged.loc[~(merged.created_dt_y >= merged.created_dt - pd.Timedelta(days=20)), 'status_y'] = np.nan

Здесь мынеобходимо отменить условие после , поскольку merged.created_dt_y содержит нули, которые не соответствуют фильтру.

Наконец, выберите нужные столбцы:

merged[['number_id', 'created_dt', 'status', 'status_y']]
# outputs:
   number_id                 created_dt status status_y
0        BBB 2018-05-18 20:28:51.388001      u      NaN
1        BBB 2018-05-19 12:28:51.388001      u      NaN
2        CCC 2018-05-19 23:28:51.388001      u      NaN
3        CCC 2018-05-20 22:28:51.388001      a      NaN
4        CCC 2018-05-21 05:28:51.388001      u      NaN
5        BBB 2018-05-21 12:28:51.388001      r      NaN
6        AAA 2018-05-24 21:28:51.388001      a      NaN
7        CCC 2018-05-30 16:28:51.388001      a        u
8        AAA 2018-05-31 23:28:51.388001      r        a
9        CCC 2018-06-01 00:28:51.388001      r        u
10       BBB 2018-06-01 11:28:51.388001      r        r
11       BBB 2018-06-01 19:28:51.388001      r        r
12       AAA 2018-06-03 14:28:51.388001      a        a
13       CCC 2018-06-04 15:28:51.388001      u        u
14       AAA 2018-06-05 06:28:51.388001      u        a
15       AAA 2018-06-05 20:28:51.388001      r        a
16       AAA 2018-06-06 04:28:51.388001      a        r
17       BBB 2018-06-06 18:28:51.388001      r        r
18       AAA 2018-06-07 15:28:51.388001      r        r
19       BBB 2018-06-08 09:28:51.388001      r        r
20       BBB 2018-06-08 21:28:51.388001      u        r
21       BBB 2018-06-09 04:28:51.388001      a        r
22       AAA 2018-06-09 16:28:51.388001      r        a
23       AAA 2018-06-12 07:28:51.388001      r        a
24       BBB 2018-06-13 03:28:51.388001      u        r
25       AAA 2018-06-14 08:28:51.388001      a        r
26       CCC 2018-06-14 14:28:51.388001      r        u
27       CCC 2018-06-15 17:28:51.388001      u        u
28       BBB 2018-06-16 02:28:51.388001      a        a
29       AAA 2018-06-16 08:28:51.388001      r        r
30       AAA 2018-06-17 02:28:51.388001      a        r

Результаты тестов:

Мы видим примерно 7-кратное улучшение производительности даже при использовании небольшого 30-строчного DataFrame

%timeit slow(df)
# outputs:
41 ms ± 1.11 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit fast(df)
# outputs:
5.69 ms ± 34 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

используемого кода:

def slow(df):
  idx = 0
  last_status_in_range = np.array([None] * len(df), dtype=str)
  for row in df.itertuples():
    created_dt = row.created_dt
    created_id = row.number_id
    last_status_in_range[idx] = get_last_status_in_range(df, created_dt, created_id)
    idx += 1
  return df.assign(prev_status=last_status_in_range)

def fast(df):
  d = df.assign(until = df.created_dt - pd.Timedelta(days=5))
  merged = pd.merge_asof(
      d, d, left_on='until', right_on='created_dt', 
      by='number_id', suffixes=('', '_y'), 
      allow_exact_matches=False
  )
  merged.loc[
      ~(merged.created_dt_y >= merged.created_dt - pd.Timedelta(days=20)), 
      'status_y'
  ] = np.nan
  return merged[['number_id', 'created_dt', 'status', 'status_y']]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...