Подтвердить что ты не робот

Вычисление разностей в группах кадра данных

Скажем, у меня есть кадр данных с тремя столбцами: 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?

4b9b3361

Ответ 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