在数据框中计算组内差异

67
假设我有一个包含三列的数据框:日期、股票代码、数值(没有索引,至少最初没有)。我有许多日期和许多股票代码,但每个(ticker, date)元组是唯一的。(但显然相同的日期会出现在许多行中,因为它将存在于多个股票代码中,并且相同的股票代码将出现在多行中,因为它将存在于多个日期中。)
最初,我的行是特定顺序,但不是按任何列排序。
我想计算每个股票代码(按日期排序)的第一次差异(每日变化),并将其放入数据框的新列中。 在这种情况下,我不能简单地这样做。
df['diffs'] = df['value'].diff()

因为相邻的行不来自同一支票券。像这样排序:

df = df.sort(['ticker', 'date'])
df['diffs'] = df['value'].diff()
解决问题,因为将会有“边界”。也就是说,在排序之后,一个代码的最后一项价值将会高于下一个代码的第一项价值。然后计算差异会导致两个代码之间的差异。我不想要这样。我希望每个代码的最早日期在其差分列中获得NaN
这似乎是使用groupby的明显时机,但出于某种原因,我似乎无法使其正常工作。明确地说,我想执行以下过程:
1. 基于它们的代码将行分组 2. 在每个组内,按其日期对行进行排序 3. 在每个排序后的组内,计算value列的差异 4. 将这些差异放入原始数据框中的新diffs列中(理想情况下保留原始数据框的顺序)。
我必须想象这是一个单行程序。但我错过了什么吗?

在2013-12-17晚上9:00编辑

好...有进展了。我可以执行以下操作以获取新数据框:

result = df.set_index(['ticker', 'date'])\
    .groupby(level='ticker')\
    .transform(lambda x: x.sort_index().diff())\
    .reset_index()

但如果我理解groupby的机制,那么我的行将首先按ticker排序,然后再按date排序。是这样吗?如果是的话,我需要执行合并操作将差异列(当前在result['current']中)追加到原始数据框df中吗?

6个回答

56

把你所描述的事情自己做不是更简单吗?

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

1
这确实是一个简洁的解决方案。我在原始帖子的编辑中提出了一种替代方案。你的方法更加简洁。话虽如此,如果保留行的原始顺序(由它们的 (ticker, date) 元组定义)很重要怎么办?你会在原始数据框的副本上使用你的解决方案,然后合并(以 tickerdate 为键)吗? - 8one6
1
我寻找一个更通用的方法的另一个原因是,一旦我拥有按日期排序、股票同质化的组,我可能想做比取第一阶差分更高级的事情。例如,我可能想在原始数据框中添加一个名为“滚动平均”的列,其中每行的值是该行指定的ticker前N个样本的平均值。 - 8one6
1
如果“日期”值在大多数“股票”之间是相同的,您可以执行以下操作:df.pivot_table( cols='ticker', rows='date', values='value' ),@DJ_8one6。 - behzad.nouri
很好的观点。但是数据空洞在股票中并不常见。例如,假设只有奇数天支持股票1,而只有偶数天支持股票2。如果按照您建议的方式进行旋转,然后计算列差异,您将得到各处的“nan”。是否有一种方法可以将我的一般方法(set_indexgroupbytransformreset_index)与您提到的重新索引相结合,以在过程结束时进行“重新对齐”?我认为groupby结构在强制执行计算不“跨组”方面具有很大的价值。 - 8one6
@DJ_8one6 我增加了一种替代方法;虽然它不使用 groupby,但可以轻松使用此方法来计算滚动平均值或类似内容。 - behzad.nouri
显示剩余6条评论

27

好的。经过深思熟虑,我认为这是我最喜欢的解决方案组合,结合了上述解决方案并进行了一些尝试。原始数据存储在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。你对此有什么想法吗?


1
sort在pandas中已被弃用。因此,那些遇到“DataFrame'对象没有'sort'属性”的警告的人可以将上面的行更改为df.sort_values(['ticker', 'date'], inplace=True) - micstr
关于你上一个问题 - 这应该可以解决 df['diffs'] = df_temp.groupby('event_press')['event_impressions'].transform('diff') - David Arenburg

14

我知道这是一个老问题,所以我假设在那个时候这个功能不存在。但是对于现在有这个问题的人来说,这个解决方案很有效:

df.sort_values(['ticker', 'date'], inplace=True)
df['diffs'] = df.groupby('ticker')['value'].diff()

为了返回原始顺序,您可以使用:

df.sort_index(inplace=True)

9
# Make sure your data is sorted properly
df = df.sort_values(by=['group_var', 'value'])

# only take diffs where next row is of the same group
df['diffs'] = np.where(df.group_var == df.group_var.shift(1), df.value.diff(), 0)

说明: 这里输入图片描述

1
请解释你的代码如何帮助回答这个问题。 - joanis

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

1
您可以使用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

感谢您的回答。对于我的应用程序,考虑每个股票“独立”非常重要。即,为了强制每个股票在数据集中拥有所有日期的值,无论是向后还是向前“填充”数据都是不合适的。相反,更适合考虑每个股票仅具备数据的日期。我很好奇您对我在上面对behzad.nouri的回答中描述的一般set_indexgroupbytransformreset_index过程的看法。 - 8one6

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接