我有以下熊猫数据框:
df = pd.DataFrame([
['A', 2017, 1],
['A', 2019, 1],
['B', 2017, 1],
['B', 2018, 1],
['C', 2016, 1],
['C', 2019, 1],
], columns=['ID', 'year', 'number'])
Run Code Online (Sandbox Code Playgroud)
并且正在寻找最有效的方法来填充缺失的年份,该列的默认值为 0 number
预期的输出是:
ID year number
0 A 2017 1
1 A 2018 0
2 A 2019 1
3 B 2017 1
4 B 2018 1
5 C 2016 1
6 C 2017 0
7 C 2018 0
8 C 2019 1
Run Code Online (Sandbox Code Playgroud)
我拥有的数据框相对较大,因此我正在寻找一种有效的解决方案。
编辑:
这是我到目前为止的代码:
min_max_dict = df[['ID', 'year']].groupby('ID').agg([min, max]).to_dict('index')
new_ix = [[], []]
for id_ in df['ID'].unique():
for year in range(min_max_dict[id_][('year', 'min')], min_max_dict[id_][('year', 'max')]+1):
new_ix[0].append(id_)
new_ix[1].append(year)
df.set_index(['ID', 'year'], inplace=True)
df = df.reindex(new_ix, fill_value=0).reset_index()
Run Code Online (Sandbox Code Playgroud)
结果
ID year number
0 A 2017 1
1 A 2018 0
2 A 2019 1
3 B 2017 1
4 B 2018 1
5 C 2016 1
6 C 2017 0
7 C 2018 0
8 C 2019 1
Run Code Online (Sandbox Code Playgroud)
Sco*_*ton 19
比使用稍快的方法explode
是使用 pd.Series 构造函数。如果年份已经从最早到最晚排序,您可以使用 .iloc。
idx = df.groupby('ID')['year'].apply(lambda x: pd.Series(np.arange(x.iloc[0], x.iloc[-1]+1))).reset_index()
df.set_index(['ID','year']).reindex(pd.MultiIndex.from_arrays([idx['ID'], idx['year']]), fill_value=0).reset_index()
Run Code Online (Sandbox Code Playgroud)
输出:
ID year number
0 A 2017 1
1 A 2018 0
2 A 2019 1
3 B 2017 1
4 B 2018 1
5 C 2016 1
6 C 2017 0
7 C 2018 0
8 C 2019 1
Run Code Online (Sandbox Code Playgroud)
ank*_*_91 11
这是另一种方法 reindex
u = df.groupby('ID')['year'].apply(lambda x: range(x.min(),x.max()+1)).explode()
out = (df.set_index(['ID','year']).reindex(u.reset_index().to_numpy(),fill_value=0)
.reset_index())
Run Code Online (Sandbox Code Playgroud)
ID year number
0 A 2017 1
1 A 2018 0
2 A 2019 1
3 B 2017 1
4 B 2018 1
5 C 2016 1
6 C 2017 0
7 C 2018 0
8 C 2019 1
Run Code Online (Sandbox Code Playgroud)
t = df.groupby('ID')['year'].agg(['min','max']).reset_index()
t['missing'] = t.transform(lambda x: [y for y in range(x['min'], x['max']+1) if y not in x.values], axis=1)
t = t[['ID','missing']].explode('missing').dropna()
t['number'] = 0
t.columns = ['ID','year','number']
pd.concat([df,t]).sort_values(by=['ID','year'])
Run Code Online (Sandbox Code Playgroud)
输出
ID year number
0 A 2017 1
0 A 2018 0
1 A 2019 1
2 B 2017 1
3 B 2018 1
4 C 2016 1
2 C 2017 0
2 C 2018 0
5 C 2019 1
Run Code Online (Sandbox Code Playgroud)
这是一种避免任何缓慢应用的方法lambda
。这是一个内存效率低下的解决方案,因为我们创建了基础 DataFrame,它是 DataFrame 中所有 ID 和年份范围的叉积。更新后,我们可以使用布尔掩码有效地将其分割为您需要的时间段。掩码是从创建的cummax
check in the forward and reverse directions.
如果大多数 ID 跨越相同的一般年份范围,那么从产品创建基础 DataFrame 时就不会造成太多浪费。如果您想要更高的性能,有很多关于更有效的方法来进行交叉产品的帖子
\ndef Alollz(df):\n idx = pd.MultiIndex.from_product([np.unique(df[\'ID\']), \n np.arange(df[\'year\'].min(), df[\'year\'].max()+1)],\n names=[\'ID\', \'year\'])\n \n df_b = pd.DataFrame({\'number\': 0}, index=idx)\n df_b.update(df.set_index([\'ID\', \'year\']))\n \n m = (df_b.groupby(level=0)[\'number\'].cummax().eq(1) \n & df_b[::-1].groupby(level=0)[\'number\'].cummax().eq(1))\n \n return df_b.loc[m].reset_index()\n
Run Code Online (Sandbox Code Playgroud)\nAlollz(df)\n\n ID year number\n0 A 2017 1.0\n1 A 2018 0.0\n2 A 2019 1.0\n3 B 2017 1.0\n4 B 2018 1.0\n5 C 2016 1.0\n6 C 2017 0.0\n7 C 2018 0.0\n8 C 2019 1.0\n
Run Code Online (Sandbox Code Playgroud)\n这肯定比其他一些提案要多得多的代码。但为了看看它真正的亮点,让我们创建一些具有 50K ID 的虚拟数据(这里,为了简化创建测试数据的目的,我将让所有数据的日期范围都相同)。
\nN = 50000\ndf = pd.DataFrame({\'ID\': np.repeat(range(N), 2),\n \'year\': np.tile([2010,2018], N),\n \'number\': 1})\n\n#@Scott Boston\'s Answer\ndef SB(df):\n idx = df.groupby(\'ID\')[\'year\'].apply(lambda x: pd.Series(np.arange(x.iloc[0], x.iloc[-1]+1))).reset_index()\n df = df.set_index([\'ID\',\'year\']).reindex(pd.MultiIndex.from_arrays([idx[\'ID\'], idx[\'year\']]), fill_value=0).reset_index()\n return df\n\n# Make sure they give the same output:\n(Alollz(df) == SB(df)).all().all()\n#True\n\n%timeit Alollz(df)\n#1.9 s \xc2\xb1 73.9 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)\n\n%timeit SB(df)\n#10.8 s \xc2\xb1 539 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)\n
Run Code Online (Sandbox Code Playgroud)\n所以速度大约快了 5 倍,这对于需要几秒的时间来说是一个相当大的问题。
\n