在此数据框中填充缺失值的最有效方法是什么?

bor*_*hev 23 python pandas

我有以下熊猫数据框:

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)

  • @anky 在此示例中,爆炸“每个循环 17.2 ms ± 332 µs(平均值 ± 标准偏差,7 次运行,每次 100 个循环)”,pd.Series 构造函数“每个循环 13.1 ms ± 208 µs(平均值 ± 标准偏差)”。开发 7 次运行,每次 100 次循环)` (3认同)

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)


Chr*_*ris 6

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)


ALo*_*llz 5

这是一种避免任何缓慢应用的方法lambda。这是一个内存效率低下的解决方案,因为我们创建了基础 DataFrame,它是 DataFrame 中所有 ID 和年份范围的叉积。更新后,我们可以使用布尔掩码有效地将其分割为您需要的时间段。掩码是从创建的cummax check in the forward and reverse directions.

\n

如果大多数 ID 跨越相同的一般年份范围,那么从产品创建基础 DataFrame 时就不会造成太多浪费。如果您想要更高的性能,有很多关于更有效的方法来进行交叉产品的帖子

\n
def 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)\n
\n
Alollz(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
\n

这肯定比其他一些提案要多得多的代码。但为了看看它真正的亮点,让我们创建一些具有 50K ID 的虚拟数据(这里,为了简化创建测试数据的目的,我将让所有数据的日期范围都相同)。

\n
N = 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