我有以下熊猫数据框:
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'])
,并且我正在寻找最有效的方法来为number
列的默认值0填充缺失的年份
预期输出为:
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
我拥有的数据帧相对较大,因此我正在寻找一种有效的解决方案。
编辑:
这是我到目前为止的代码:
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()
结果
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
答案 0 :(得分: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()
输出:
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
答案 1 :(得分: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())
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
答案 2 :(得分: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'])
输出
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
答案 3 :(得分:4)
这是一种方法:
letter_keys = df.ID.unique()
data = df.values
missing_records = []
for letter in letter_keys:
print(letter)
years = [x[1] for x in data if x[0] == letter]
min_year = min(years)
max_year = max(years)
current_year = min_year
while current_year<max_year:
if current_year not in years:
missing_records.append([letter, current_year,0])
print('missing', current_year)
current_year +=1
new_df = df.append(pd.DataFrame(missing_records, columns = df.columns)).sort_values(['ID','year'])
输出
| ID | year | number |
|:-----|-------:|---------:|
| A | 2017 | 1 |
| A | 2018 | 0 |
| A | 2019 | 1 |
| B | 2017 | 1 |
| B | 2018 | 1 |
| C | 2016 | 1 |
| C | 2017 | 0 |
| C | 2018 | 0 |
| C | 2019 | 1 |
答案 4 :(得分:4)
这里是一种避免lambda
缓慢应用的方法。从我们创建基本DataFrame的角度来看,这是内存效率低下的解决方案,它是所有ID和您DataFrame中年份范围的叉积。更新后,我们可以使用布尔掩码有效将其切成所需的时间段。通过cummax
向前和向后的检查来创建蒙版。
如果大多数ID跨越相同的一般年份范围,那么从产品中创建基本DataFrame不会有太多浪费。如果您想获得更高的性能,有很多关于more efficient ways to do a cross-product
的帖子def Alollz(df):
idx = pd.MultiIndex.from_product([np.unique(df['ID']),
np.arange(df['year'].min(), df['year'].max()+1)],
names=['ID', 'year'])
df_b = pd.DataFrame({'number': 0}, index=idx)
df_b.update(df.set_index(['ID', 'year']))
m = (df_b.groupby(level=0)['number'].cummax().eq(1)
& df_b[::-1].groupby(level=0)['number'].cummax().eq(1))
return df_b.loc[m].reset_index()
Alollz(df)
ID year number
0 A 2017 1.0
1 A 2018 0.0
2 A 2019 1.0
3 B 2017 1.0
4 B 2018 1.0
5 C 2016 1.0
6 C 2017 0.0
7 C 2018 0.0
8 C 2019 1.0
这肯定比其他一些建议多得多的代码。但是,要查看它真正发挥作用的地方,让我们创建一些具有50K ID的虚拟数据(此处,为了简化测试数据的创建,我将让所有日期范围都相同)。
N = 50000
df = pd.DataFrame({'ID': np.repeat(range(N), 2),
'year': np.tile([2010,2018], N),
'number': 1})
#@Scott Boston's Answer
def SB(df):
idx = df.groupby('ID')['year'].apply(lambda x: pd.Series(np.arange(x.iloc[0], x.iloc[-1]+1))).reset_index()
df = df.set_index(['ID','year']).reindex(pd.MultiIndex.from_arrays([idx['ID'], idx['year']]), fill_value=0).reset_index()
return df
# Make sure they give the same output:
(Alollz(df) == SB(df)).all().all()
#True
%timeit Alollz(df)
#1.9 s ± 73.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit SB(df)
#10.8 s ± 539 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
所以速度要快大约5倍,这与处理耗时几秒钟有关。
答案 5 :(得分:2)
您可以尝试使用date_range
和pd.merge
:
g = df.groupby("ID")["year"].agg({"min":"min","max":"max"}).reset_index()
id_years = pd.DataFrame(list(g.apply(lambda row: list(row["ID"]) +
list(pd.date_range(start=f"01/01/{row['min']}", \
end=f"01/01/{row['max']+1}",freq='12M').year), axis=1))).melt(0).dropna()[[0,"value"]]
id_years.loc[:,"value"] = id_years["value"].astype(int)
id_years = id_years.rename(columns = {0:"ID","value":'year'})
id_years = id_years.sort_values(["ID","year"]).reset_index(drop=True)
## Merge two dataframe
output_df = pd.merge(id_years, df, on=["ID","year"], how="left").fillna(0)
output_df.loc[:,"number"] = output_df["number"].astype(int)
output_df
输出:
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
答案 6 :(得分:1)
这会起作用,但是会为“ B”创建一个“ 2019”条目:
df.pivot(index='ID', columns='year', values='number').fillna(0).stack().to_frame('number')
返回:
number
ID year
A 2016 0.0
2017 1.0
2018 0.0
2019 1.0
B 2016 0.0
2017 1.0
2018 1.0
2019 0.0
C 2016 1.0
2017 0.0
2018 0.0
2019 1.0