Вычисление разностей в группах кадра данных
Скажем, у меня есть кадр данных с тремя столбцами: Date, Ticker, Value (без индекса, по крайней мере, для начала). У меня много дат и много тикеров, но каждый набор (ticker, date)
уникален. (Но, очевидно, одна и та же дата будет отображаться во многих строках, так как она будет там для нескольких тикеров, и один и тот же тикер появится в нескольких строках, так как он будет там для многих дат.)
Первоначально мои строки в определенном порядке, но не отсортированы ни по одному из столбцов.
Я хотел бы вычислить первые отличия (ежедневные изменения) каждого тикера (упорядоченные по дате) и поместить их в новый столбец в моем фреймворке. Учитывая этот контекст, я не могу просто
df['diffs'] = df['value'].diff()
поскольку соседние строки не принадлежат одному тикеру. Сортировка:
df = df.sort(['ticker', 'date'])
df['diffs'] = df['value'].diff()
не решает проблему, потому что будут "границы". То есть после этого сортировки последнее значение для одного тикера будет превышать первое значение для следующего тикера. И тогда вычислительные различия будут иметь значение между двумя тикерами. Я не хочу этого. Я хочу, чтобы самая ранняя дата каждого тикера завершилась с NaN
в столбце diff.
Это кажется очевидным временем для использования groupby
, но по какой-то причине я не могу заставить его работать правильно. Чтобы быть ясным, я хотел бы выполнить следующий процесс:
- Группировать строки на основе их
ticker
- В каждой группе сортируйте строки по
date
- В каждой сортированной группе вычисляйте различия столбца
value
- Поместите эти различия в исходный блок данных в новый столбец
diffs
(в идеале оставляя исходный порядок данных в такте.)
Я должен представить, что это однострочный. Но чего мне не хватает?
Редактировать в 21:00 2013-12-17
Хорошо... некоторый прогресс. Я могу сделать следующее, чтобы получить новый фреймворк данных:
result = df.set_index(['ticker', 'date'])\
.groupby(level='ticker')\
.transform(lambda x: x.sort_index().diff())\
.reset_index()
Но если я понимаю механику groupby, мои строки теперь будут отсортированы сначала на ticker
, а затем на date
. Это верно? Если это так, мне нужно выполнить слияние, чтобы добавить столбец различий (в настоящее время в result['current']
к исходному файловому кадру df
?
Ответы
Ответ 1
было бы не просто сделать то, что вы сами описали, а именно
df.sort(['ticker', 'date'], inplace=True)
df['diffs'] = df['value'].diff()
а затем исправьте границы:
mask = df.ticker != df.ticker.shift(1)
df['diffs'][mask] = np.nan
чтобы сохранить исходный индекс, который вы можете сделать idx = df.index
в начале, а затем в конце вы можете сделать df.reindex(idx)
или, если он является огромным фреймворком данных, выполните операции над
df.filter(['ticker', 'date', 'value'])
а затем join
два кадра данных в конце.
изменить: в качестве альтернативы (хотя все еще не используется groupby
)
df.set_index(['ticker','date'], inplace=True)
df.sort_index(inplace=True)
df['diffs'] = np.nan
for idx in df.index.levels[0]:
df.diffs[idx] = df.value[idx].diff()
для
date ticker value
0 63 C 1.65
1 88 C -1.93
2 22 C -1.29
3 76 A -0.79
4 72 B -1.24
5 34 A -0.23
6 92 B 2.43
7 22 A 0.55
8 32 A -2.50
9 59 B -1.01
это произведет:
value diffs
ticker date
A 22 0.55 NaN
32 -2.50 -3.05
34 -0.23 2.27
76 -0.79 -0.56
B 59 -1.01 NaN
72 -1.24 -0.23
92 2.43 3.67
C 22 -1.29 NaN
63 1.65 2.94
88 -1.93 -3.58
Ответ 2
Ok. Много размышлений об этом, и я думаю, что это моя любимая комбинация решений выше и немного поиграть. Исходные данные живут в df
:
df.sort(['ticker', 'date'], inplace=True)
# for this example, with diff, I think this syntax is a bit clunky
# but for more general examples, this should be good. But can we do better?
df['diffs'] = df.groupby(['ticker'])['value'].transform(lambda x: x.diff())
df.sort_index(inplace=True)
Это выполнит все, что я хочу. И мне действительно нравится, что его можно обобщить на случаи, когда вы хотите применить более сложную функцию, чем diff
. В частности, вы могли бы сделать такие вещи, как lambda x: pd.rolling_mean(x, 20, 20)
, чтобы сделать столбец качения, где вам не нужно беспокоиться о том, что данные каждого тикера повреждены, как и любого другого тикера (groupby
позаботится об этом для вас..).
Итак, вот вопрос, который мне оставил... почему не работает следующая работа для строки, начинающейся с df['diffs']
:
df['diffs'] = df.groupby[('ticker')]['value'].transform(np.diff)
когда я это делаю, я получаю столбец diffs
, полный 0. Любые мысли об этом?
Ответ 3
Вот решение, основанное на том, что написал @behzad.nouri, но используя pd.IndexSlice
:
df = df.set_index(['ticker', 'date']).sort_index()[['value']]
df['diff'] = np.nan
idx = pd.IndexSlice
for ix in df.index.levels[0]:
df.loc[ idx[ix,:], 'diff'] = df.loc[idx[ix,:], 'value' ].diff()
Для:
> df
date ticker value
0 63 C 1.65
1 88 C -1.93
2 22 C -1.29
3 76 A -0.79
4 72 B -1.24
5 34 A -0.23
6 92 B 2.43
7 22 A 0.55
8 32 A -2.50
9 59 B -1.01
Он возвращает:
> df
value diff
ticker date
A 22 0.55 NaN
32 -2.50 -3.05
34 -0.23 2.27
76 -0.79 -0.56
B 59 -1.01 NaN
72 -1.24 -0.23
92 2.43 3.67
C 22 -1.29 NaN
63 1.65 2.94
88 -1.93 -3.58
Ответ 4
Вы можете использовать pivot
для преобразования данных в таблицу даты-тикера, вот пример:
сначала создайте тестовые данные:
import pandas as pd
import numpy as np
import random
from itertools import product
dates = pd.date_range(start="2013-12-01", periods=10).to_native_types()
ticks = "ABCDEF"
pairs = list(product(dates, ticks))
random.shuffle(pairs)
pairs = pairs[:-5]
values = np.random.rand(len(pairs))
dates, ticks = zip(*pairs)
df = pd.DataFrame({"date":dates, "tick":ticks, "value":values})
преобразовать формат данных в формате pivot
:
df2 = df.pivot(index="date", columns="tick", values="value")
заполните NaN:
df2 = df2.fillna(method="ffill")
вызов diff()
метод:
df2.diff()
вот что выглядит df2
:
tick A B C D E F
date
2013-12-01 0.077260 0.084008 0.711626 0.071267 0.811979 0.429552
2013-12-02 0.106349 0.141972 0.457850 0.338869 0.721703 0.217295
2013-12-03 0.330300 0.893997 0.648687 0.628502 0.543710 0.217295
2013-12-04 0.640902 0.827559 0.243816 0.819218 0.543710 0.190338
2013-12-05 0.263300 0.604084 0.655723 0.299913 0.756980 0.135087
2013-12-06 0.278123 0.243264 0.907513 0.723819 0.506553 0.717509
2013-12-07 0.960452 0.243264 0.357450 0.160799 0.506553 0.194619
2013-12-08 0.670322 0.256874 0.637153 0.582727 0.628581 0.159636
2013-12-09 0.226519 0.284157 0.388755 0.325461 0.957234 0.810376
2013-12-10 0.958412 0.852611 0.472012 0.832173 0.957234 0.723234