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

Вот пример DataFrame, который я буду использовать, чтобы лучше проиллюстрировать мой вопрос:

import pandas as pd

df = pd.DataFrame(pd.np.random.rand(30, 3), columns=tuple('ABC'))
df['event'] = pd.np.nan
df.loc[10, 'event'] = 'ping'
df.loc[20, 'event'] = 'ping'
df.loc[19, 'event'] = 'pong'

Мне нужно создать окна из n строк, сосредоточенных вокруг каждого вхождения ping,

Другими словами, пусть i быть индексом строки, содержащей ping в event колонка. Для каждого iХочу выбрать df.ix[i-n:i+n],

Таким образом, для n=3Я бы ожидал следующий результат:

             A          B          C event
7    0.8295863  0.2162861  0.4856461   NaN
8     0.156646  0.4730667  0.9968878   NaN
9    0.6709413  0.4796197  0.8747416   NaN
10  0.09942329   0.154008  0.5761598  ping
11   0.7168143   0.678207  0.7281105   NaN
12   0.8915475  0.8013187  0.9049722   NaN
13   0.9545411  0.4844835  0.1645746   NaN
17   0.9909208  0.1091025  0.6582635   NaN
18   0.2536326  0.4324749  0.8001643   NaN
19   0.4734659  0.5582809  0.1221296  pong
20   0.7230407  0.6695843  0.3902591  ping
21   0.3624909  0.2685049  0.5484445   NaN
22  0.05626284  0.6113877  0.9131929   NaN
23   0.8312294  0.5694373  0.4325798   NaN

[14 rows x 4 columns]

Несколько предостережений:

  1. Я ищу не итеративное решение.
  2. Обратите внимание, что есть pong значение, вокруг которого мы не хотим центрировать окно. Это захвачено в результате центрирования вокруг второго ping, тем не мение.

Как этого достичь?

3 ответа

Решение
In [17]: n = 3

Выберите индексатор, который является диапазоном того, что вам нужно, например, целевой индекс +- 3 (в зависимости от максимального / минимального размера кадра). Объедините их всех и устраните дуплексы.

In [18]: indexers = np.unique(np.concatenate([ np.arange(max(i-n,0),min(i+n,len(df))) for i in df[df.event=='ping'].index ]))

In [19]: indexers
Out[19]: array([ 7,  8,  9, 10, 11, 12, 17, 18, 19, 20, 21, 22])

Выберите их.

In [20]: df.iloc[indexers]
Out[20]: 
             A           B          C event
7   0.03348742  0.05735324  0.1220022   NaN
8    0.9567363   0.6539097  0.8409577   NaN
9    0.3115902   0.4955503  0.1749197   NaN
10   0.6883777   0.6185107  0.7933182  ping
11   0.5185129   0.6533616  0.1569159   NaN
12   0.1196976   0.9638604  0.7318006   NaN
17  0.02897615   0.1224485  0.5706852   NaN
18  0.02409971   0.4715463  0.4587161   NaN
19   0.9070592   0.3371241  0.9543977  pong
20   0.8533369   0.7549413  0.5334882  ping
21   0.9546738   0.8203931  0.8543028   NaN
22  0.05691086   0.2402766  0.3922318   NaN

Обратите внимание, что вам может потребоваться сделать df.reset_index() (прежде чем выбрать, чтобы получить фактическую позицию индекса строки, а не значение).

Обратите внимание, что здесь есть ошибка, так как установка столбца 'event' преобразует все в объект, см. Здесь. Вы можете облегчить с помощью df.convert_objects(),

Может быть:

>>> ts, n = df['event'] == 'ping', 3
>>> idx = ts.shift(n).fillna(False)  # +n rows
>>> for j in range(-n, n):  # -n to n-1 rows
...     idx |= ts.shift(j).fillna(False)
... 
>>> df[idx]

Один из способов сделать это - использовать вложенные предложения np.where. Это не самый красивый код, но он делает свое дело.

ping = pd.Series(np.where(df.event == 'ping', True,
                          np.where(df.event.shift(1) == 'ping', True,
                                   np.where(df.event.shift(-1) == 'ping', True, False))), index=df.index)

df[ping]

Может ли кто-нибудь помочь мне перенести случай i=1 на общий случай?

Изменить: На самом деле, они не должны быть вложенными. Это будет делать:

ping = pd.Series(np.where((df.event == 'ping') | (df.event.shift(1) == 'ping') |
                      (df.event.shift(-1) == 'ping'), True, False), index=df.index)
Другие вопросы по тегам