具有基于索引的限制的前向填充列

Posted

技术标签:

【中文标题】具有基于索引的限制的前向填充列【英文标题】:Forward fill column with an index-based limit 【发布时间】:2019-06-18 20:16:14 【问题描述】:

我想向前填充一列并且我想指定一个限制,但我希望限制基于索引——而不是像限制允许的简单行数。

例如,假设我有以下给出的数据框:

df = pd.DataFrame(
    'data': [0.0, 1.0, np.nan, 3.0, np.nan, 5.0, np.nan, np.nan, np.nan, np.nan],
    'group': [0, 0, 0, 1, 1, 0, 0, 0, 1, 1]
)

看起来像

In [27]: df
Out[27]:
   data  group
0   0.0      0
1   1.0      0
2   NaN      0
3   3.0      1
4   NaN      1
5   5.0      0
6   NaN      0
7   NaN      0
8   NaN      1
9   NaN      1

如果我按group 列分组并用limit=2 向前填充该组,那么我的结果数据框将是

In [35]: df.groupby('group').ffill(limit=2)
Out[35]:
   group  data
0      0   0.0
1      0   1.0
2      0   1.0
3      1   3.0
4      1   3.0
5      0   5.0
6      0   5.0
7      0   5.0
8      1   3.0
9      1   NaN

然而,我在这里真正想做的只是向前填充到索引在每个组的第一个索引的 2 以内的行,而不是每个组的接下来的 2 行。例如,如果我们只查看数据框上的组:

In [36]: for i, group in df.groupby('group'):
    ...:     print(group)
    ...:
   data  group
0   0.0      0
1   1.0      0
2   NaN      0
5   5.0      0
6   NaN      0
7   NaN      0
   data  group
3   3.0      1
4   NaN      1
8   NaN      1
9   NaN      1

我希望这里的第二组仅向前填充到索引 4——而不是 8 和 9。第一组的 NaN 值都在最后一个非 NaN 值的 2 个索引内,因此它们将被完全填充.生成的数据框如下所示:

   group  data
0      0   0.0
1      0   1.0
2      0   1.0
3      1   3.0
4      1   3.0
5      0   5.0
6      0   5.0
7      0   5.0
8      1   NaN
9      1   NaN

FWIW 在我的实际用例中,我的索引是 DateTimeIndex(并且已排序)。

我目前有一个可行的解决方案,需要遍历在组索引上过滤的数据帧,根据索引为每个具有非 NaN 值的单个事件创建一个时间范围,然后将它们组合起来。但这太慢了,不实用。

【问题讨论】:

修改您的示例以使用 DateTimeIndex 并设计出实际满足您希望通过有效答案满足的所有条件的值会很有帮助。 @unutbu 确实,很难正确模拟。但我同意这样的例子对未来的读者更有益。我试图更笼统,但这只会增加对有限(如果有的话)好处的混淆。 【参考方案1】:
import numpy as np
import pandas as pd
df = pd.DataFrame(
    'data': [0.0, 1.0, 1, 3.0, np.nan, 22, np.nan, 5, np.nan, np.nan],
    'group': [0, 0, 1, 0, 1, 0, 1, 0, 1, 1])

df = df.reset_index()
df['stop_index'] = df['index'] + 2
df['stop_index'] = df['stop_index'].where(pd.notnull(df['data']))
df['stop_index'] = df.groupby('group')['stop_index'].ffill()
df['mask'] = df['index'] <= df['stop_index']
df.loc[df['mask'], 'data'] = df.groupby('group')['data'].ffill()
print(df)
#    index  data  group  stop_index   mask
# 0      0   0.0      0         2.0   True
# 1      1   1.0      0         3.0   True
# 2      2   1.0      1         4.0   True
# 3      3   3.0      0         5.0   True
# 4      4   1.0      1         4.0   True
# 5      5  22.0      0         7.0   True
# 6      6   NaN      1         4.0  False
# 7      7   5.0      0         9.0   True
# 8      8   NaN      1         4.0  False
# 9      9   NaN      1         4.0  False

# clean up df
df = df[['data', 'group']]
print(df)

产量

   data  group
0   0.0      0
1   1.0      0
2   1.0      1
3   3.0      0
4   1.0      1
5  22.0      0
6   NaN      1
7   5.0      0
8   NaN      1
9   NaN      1

这会将索引复制到列中,然后 创建第二个stop_index 列,它是index 的大小增加 (时间)窗口。

df = df.reset_index()
df['stop_index'] = df['index'] + 2

然后它使stop_index 中的空行匹配data 中的空行:

df['stop_index'] = df['stop_index'].where(pd.notnull(df['data']))

然后它基于每个组向前填充stop_index

df['stop_index'] = df.groupby('group')['stop_index'].ffill()

现在(终于)我们可以定义所需的mask——我们真正想要前向填充data的地方:

df['mask'] = df['index'] <= df['stop_index']
df.loc[df['mask'], 'data'] = df.groupby('group')['data'].ffill()

【讨论】:

嗨,您想测试我回答中的测试数据吗?我无法匹配输出..【参考方案2】:

IIUC

l=[]
for i, group in df.groupby('group'):
    idx=group.index
    l.append(group.reindex(df.index).ffill(limit=2).loc[idx])

pd.concat(l).sort_index()
   data  group
0   0.0    0.0
1   1.0    0.0
2   1.0    0.0
3   3.0    1.0
4   3.0    1.0
5   5.0    0.0
6   5.0    0.0
7   5.0    0.0
8   NaN    1.0
9   NaN    1.0

测试数据

   data  group
0   0.0      0
1   1.0      0
2   1.0      1
3   3.0      0
4   NaN      1
5   22       0
6   NaN      1
7   5.0      0
8   NaN      1
9   NaN      1

我的数据测试方法

   data  group
0   0.0    0.0
1   1.0    0.0
2   1.0    1.0
3   3.0    0.0
4   1.0    1.0
5  22.0    0.0
6   NaN    1.0# here not change , since the previous two do not have valid value for group 1 
7   5.0    0.0
8   NaN    1.0
9   NaN    1.0

用 unutbu 输出

   data  group
0   0.0      0
1   1.0      0
2   1.0      1
3   3.0      0
4   1.0      1
5  22.0      0
6   1.0      1# miss match in here
7   5.0      0
8   NaN      1
9   NaN      1

【讨论】:

我在想 reindex 可能与解决方案有关。能稍微解释一下方法链吗? @AlexanderReynolds 从 sub-df 重新索引到原始 df 的 index ,所有未显示在 sub-df 中的行都将是 NaN ,那么我们只需要正常填充 limit ,因为索引在之后继续重新索引 哦,这很有意义!是的,您已经用另一个答案突出了问题并正确理解了我。我将给它一点时间,看看是否有任何方法可以在不显式使用 groupby 索引的情况下做到这一点。顺便说一句,您可以使用 df.groupby(...).groups 这是一个字典,其中值是索引,而不是手动使用 group.index 将它们拉出来。所以for idx in df.groupby(...).groups.values(). 嗯...实际上,再想一想,我也不认为这就是答案。问题是这仍然索引具有此功能的 行数,而不是基于索引的 的任意截断,不是吗? @AlexanderReynolds 你能在一些边缘情况下运行该方法,看看是否有效

以上是关于具有基于索引的限制的前向填充列的主要内容,如果未能解决你的问题,请参考以下文章

前向填充多列可重用功能代码

Pandas:使用日期时间索引进行分组前向填充

20210421-C++的前向声明

高效的前向填充 bigquery

MySQL索引

前向填充特定行的特定列