从大型数据集中的对列中选择最后一个有效数据日期
Pick last valid data dates from pair columns in a large dataset
我有一个如下所示的数据框,其中第一列包含日期,其他列包含这些日期的数据:
date k1-v1 k1-v2 k2-v1 k2-v2 k1k3-v1 k1k3-v2 k4-v1 k4-v2
0 2021-01-05 2.0 7.0 NaN NaN NaN NaN 9.0 6.0
1 2021-01-31 NaN NaN 8.0 5.0 NaN NaN 7.0 6.0
2 2021-02-15 9.0 5.0 NaN 3.0 4.0 NaN NaN NaN
3 2021-02-28 NaN 9.0 0.0 1.0 NaN NaN 8.0 8.0
4 2021-03-20 7.0 NaN NaN NaN NaN NaN NaN NaN
5 2021-03-31 NaN NaN 8.0 NaN 3.0 NaN 8.0 0.0
6 2021-04-10 NaN NaN 7.0 6.0 NaN NaN NaN 9.0
7 2021-04-30 NaN 6.0 NaN NaN NaN NaN 1.0 NaN
8 2021-05-14 8.0 NaN 3.0 3.0 4.0 NaN NaN NaN
9 2021-05-31 NaN NaN 2.0 1.0 NaN NaN NaN NaN
列总是成对出现:(
k1-v1
,
k1-v2
)
;(
k2-v1
,
k2-v2
)
;(
k1k3-v1
,
k1k3-v2
)
等等 N 对。但是这对列并不总是按该顺序排列。所以k1-v1后面不一定只有k1-v2,而是会有k1-v2 数据框中某处的列。为简单起见,我并排展示了它们。
我需要在每对列中找到最后有效数据date,并总结如下所示:
keys v1-last v2-last
0 k1 2021-05-14 2021-04-30
1 k2 2021-05-31 2021-05-31
2 k1k3 2021-05-14 NaN
3 k4 2021-04-30 2021-04-10
因此对于 (
k1-v1
)
最后有效数据是日期 2021-05-14
的 8.0
,因为(
k2-v2
)
6.0
在 2021-04-30
。 v1-last
和 v2-last
中的列相应地填充了 k1,其他人也是如此。
目前我正在这样做,这在较大的数据集上不是很有效:
df.set_index('date', inplace=True)
unique_cols = set([col[0] for col in df.columns.str.split('-')])
summarized_data = []
for col in unique_cols:
pair_df = df.loc[:,[col+'-v1',col+'-v2']].dropna(how='all')
v1_last_valid = pair_df.iloc[:,0].last_valid_index()
v2_last_valid = pair_df.iloc[:,1].last_valid_index()
summarized_data.append([col, v1_last_valid, v2_last_valid])
summarized_df = pd.DataFrame(summarized_data, columns=['keys','v1-last','v2-last'])
这目前有效,并给出了预期的结果,但在大数据集上 运行 时会花费大量时间。是否可以避免循环并以不同且有效的方式完成?
我们可以颠倒列名并使用 pd.wide_to_long
,其中存根名称为 v_j
,标识符为 date
,我们将 k*
称为 keys
在结果中。然后我们可以 groupby keys
并聚合 DataFrame.last_valid_index
:
# reverse the column names
df.columns = df.columns.str.replace(r"(\w+)-(\w+)", r"-", regex=True)
# wide to long (and then make `keys` a column with reset_index)
long = pd.wide_to_long(df, stubnames=["v1", "v2"], i="date", j="keys",
sep="-", suffix=r"\w+").reset_index("keys")
# get the last valid dates & add a suffix
result = (long.groupby("keys")
.agg(pd.DataFrame.last_valid_index)
.add_suffix("-last"))
得到
>>> result
v1-last v2-last
keys
k1 2021-05-14 2021-04-30
k1k3 2021-05-14 None
k2 2021-05-31 2021-05-31
k4 2021-04-30 2021-04-10
要使 v_j
的存根名称更通用:
stubnames = df.columns.str.extract(r"^(\w+)-", expand=False).dropna().unique()
# Index(["v1", "v2"], dtype="object")
重命名该列,然后使用 wide_to_long
重组数据框。 Stack
删除 NAN
。然后使用groupby-agg
提取最后一个值。
df2 = (
pd.wide_to_long(
df2.rename(columns=(lambda x: ''.join(x.split('-')[::-1]))),
stubnames=['v2', 'v1'],
i='date',
j='keys',
suffix='.*'
).stack()
.reset_index(0)
.groupby(level=[0, 1])
.agg({'date': 'last'})
.unstack(-1)
).add_suffix('-last')
df2.columns = df2.columns.droplevel()
输出:
v2-last v1-last
keys
k1 2021-04-30 2021-05-14
k1k3 NaN 2021-05-14
k2 2021-05-31 2021-05-31
k4 2021-04-10 2021-04-30
解决方案
s = df.set_index('date').stack()
s = s.reset_index().drop_duplicates('level_1', keep='last')
s[['keys', 'val']] = s['level_1'].str.split('-', expand=True)
s = s.pivot('keys', 'val', 'date').add_suffix('-last')
解释
将数据帧的索引设置为 date
和 stack
以重塑
date
2021-01-05 k1-v1 2.0
k1-v2 7.0
k4-v1 9.0
k4-v2 6.0
2021-01-31 k2-v1 8.0
k2-v2 5.0
k4-v1 7.0
k4-v2 6.0
...
2021-05-31 k2-v1 2.0
k2-v2 1.0
dtype: float64
重置索引并删除 level_1
中具有重复值的行
date level_1 0
24 2021-04-10 k4-v2 9.0
25 2021-04-30 k1-v2 6.0
26 2021-04-30 k4-v1 1.0
27 2021-05-14 k1-v1 8.0
30 2021-05-14 k1k3-v1 4.0
31 2021-05-31 k2-v1 2.0
32 2021-05-31 k2-v2 1.0
Split
level_1
列中的字符串以创建另外两个列 keys
和 val
date level_1 0 keys val
24 2021-04-10 k4-v2 9.0 k4 v2
25 2021-04-30 k1-v2 6.0 k1 v2
26 2021-04-30 k4-v1 1.0 k4 v1
27 2021-05-14 k1-v1 8.0 k1 v1
30 2021-05-14 k1k3-v1 4.0 k1k3 v1
31 2021-05-31 k2-v1 2.0 k2 v1
32 2021-05-31 k2-v2 1.0 k2 v2
Pivot
要重塑并向列名称添加后缀 -last
的数据框
val v1-last v2-last
keys
k1 2021-05-14 2021-04-30
k1k3 2021-05-14 NaN
k2 2021-05-31 2021-05-31
k4 2021-04-30 2021-04-10
首先,定义一个函数来获取任何给定列名所需的日期:
def last_date(col):
idx = df[col].last_valid_index()
return df.loc[idx, 'date'] if idx is not None else np.nan
然后将键分组到字典中,并使用它来构建生成的数据框:
keys = {}
for col in df.columns[1:]:
key, vn = col.split('-')
keys.setdefault(key, [None]*2)[int(vn[-1])-1] = last_date(col)
pd.DataFrame.from_records([[k] + dt_list for k, dt_list in keys.items()], columns=['keys', 'v1-last', 'v2-last'])
这会产生所需的输出:
keys v1-last v2-last
0 k1 2021-05-14 2021-04-30
1 k2 2021-05-31 2021-05-31
2 k1k3 2021-05-14 NaN
3 k4 2021-04-30 2021-04-10
结合@MustafaAydin 和@ShubhamSharma 的想法,我们可以做到这一点:
temp = df.set_index('date')
应用pd.Series.last_valid_index
:
# you could use `agg` since it is a reducer
temp = temp.apply(pd.Series.last_valid_index)
将索引转换为 MultiIndex:
temp.index = temp.index.str.split("-", expand = True)
取消堆叠并添加后缀:
temp.unstack().add_suffix('_last')
v1_last v2_last
k1 2021-05-14 2021-04-30
k1k3 2021-05-14 None
k2 2021-05-31 2021-05-31
k4 2021-04-30 2021-04-10
我有一个如下所示的数据框,其中第一列包含日期,其他列包含这些日期的数据:
date k1-v1 k1-v2 k2-v1 k2-v2 k1k3-v1 k1k3-v2 k4-v1 k4-v2
0 2021-01-05 2.0 7.0 NaN NaN NaN NaN 9.0 6.0
1 2021-01-31 NaN NaN 8.0 5.0 NaN NaN 7.0 6.0
2 2021-02-15 9.0 5.0 NaN 3.0 4.0 NaN NaN NaN
3 2021-02-28 NaN 9.0 0.0 1.0 NaN NaN 8.0 8.0
4 2021-03-20 7.0 NaN NaN NaN NaN NaN NaN NaN
5 2021-03-31 NaN NaN 8.0 NaN 3.0 NaN 8.0 0.0
6 2021-04-10 NaN NaN 7.0 6.0 NaN NaN NaN 9.0
7 2021-04-30 NaN 6.0 NaN NaN NaN NaN 1.0 NaN
8 2021-05-14 8.0 NaN 3.0 3.0 4.0 NaN NaN NaN
9 2021-05-31 NaN NaN 2.0 1.0 NaN NaN NaN NaN
列总是成对出现:(
k1-v1
,
k1-v2
)
;(
k2-v1
,
k2-v2
)
;(
k1k3-v1
,
k1k3-v2
)
等等 N 对。但是这对列并不总是按该顺序排列。所以k1-v1后面不一定只有k1-v2,而是会有k1-v2 数据框中某处的列。为简单起见,我并排展示了它们。
我需要在每对列中找到最后有效数据date,并总结如下所示:
keys v1-last v2-last
0 k1 2021-05-14 2021-04-30
1 k2 2021-05-31 2021-05-31
2 k1k3 2021-05-14 NaN
3 k4 2021-04-30 2021-04-10
因此对于 (
k1-v1
)
最后有效数据是日期 2021-05-14
的 8.0
,因为(
k2-v2
)
6.0
在 2021-04-30
。 v1-last
和 v2-last
中的列相应地填充了 k1,其他人也是如此。
目前我正在这样做,这在较大的数据集上不是很有效:
df.set_index('date', inplace=True)
unique_cols = set([col[0] for col in df.columns.str.split('-')])
summarized_data = []
for col in unique_cols:
pair_df = df.loc[:,[col+'-v1',col+'-v2']].dropna(how='all')
v1_last_valid = pair_df.iloc[:,0].last_valid_index()
v2_last_valid = pair_df.iloc[:,1].last_valid_index()
summarized_data.append([col, v1_last_valid, v2_last_valid])
summarized_df = pd.DataFrame(summarized_data, columns=['keys','v1-last','v2-last'])
这目前有效,并给出了预期的结果,但在大数据集上 运行 时会花费大量时间。是否可以避免循环并以不同且有效的方式完成?
我们可以颠倒列名并使用 pd.wide_to_long
,其中存根名称为 v_j
,标识符为 date
,我们将 k*
称为 keys
在结果中。然后我们可以 groupby keys
并聚合 DataFrame.last_valid_index
:
# reverse the column names
df.columns = df.columns.str.replace(r"(\w+)-(\w+)", r"-", regex=True)
# wide to long (and then make `keys` a column with reset_index)
long = pd.wide_to_long(df, stubnames=["v1", "v2"], i="date", j="keys",
sep="-", suffix=r"\w+").reset_index("keys")
# get the last valid dates & add a suffix
result = (long.groupby("keys")
.agg(pd.DataFrame.last_valid_index)
.add_suffix("-last"))
得到
>>> result
v1-last v2-last
keys
k1 2021-05-14 2021-04-30
k1k3 2021-05-14 None
k2 2021-05-31 2021-05-31
k4 2021-04-30 2021-04-10
要使 v_j
的存根名称更通用:
stubnames = df.columns.str.extract(r"^(\w+)-", expand=False).dropna().unique()
# Index(["v1", "v2"], dtype="object")
重命名该列,然后使用 wide_to_long
重组数据框。 Stack
删除 NAN
。然后使用groupby-agg
提取最后一个值。
df2 = (
pd.wide_to_long(
df2.rename(columns=(lambda x: ''.join(x.split('-')[::-1]))),
stubnames=['v2', 'v1'],
i='date',
j='keys',
suffix='.*'
).stack()
.reset_index(0)
.groupby(level=[0, 1])
.agg({'date': 'last'})
.unstack(-1)
).add_suffix('-last')
df2.columns = df2.columns.droplevel()
输出:
v2-last v1-last
keys
k1 2021-04-30 2021-05-14
k1k3 NaN 2021-05-14
k2 2021-05-31 2021-05-31
k4 2021-04-10 2021-04-30
解决方案
s = df.set_index('date').stack()
s = s.reset_index().drop_duplicates('level_1', keep='last')
s[['keys', 'val']] = s['level_1'].str.split('-', expand=True)
s = s.pivot('keys', 'val', 'date').add_suffix('-last')
解释
将数据帧的索引设置为 date
和 stack
以重塑
date
2021-01-05 k1-v1 2.0
k1-v2 7.0
k4-v1 9.0
k4-v2 6.0
2021-01-31 k2-v1 8.0
k2-v2 5.0
k4-v1 7.0
k4-v2 6.0
...
2021-05-31 k2-v1 2.0
k2-v2 1.0
dtype: float64
重置索引并删除 level_1
date level_1 0
24 2021-04-10 k4-v2 9.0
25 2021-04-30 k1-v2 6.0
26 2021-04-30 k4-v1 1.0
27 2021-05-14 k1-v1 8.0
30 2021-05-14 k1k3-v1 4.0
31 2021-05-31 k2-v1 2.0
32 2021-05-31 k2-v2 1.0
Split
level_1
列中的字符串以创建另外两个列 keys
和 val
date level_1 0 keys val
24 2021-04-10 k4-v2 9.0 k4 v2
25 2021-04-30 k1-v2 6.0 k1 v2
26 2021-04-30 k4-v1 1.0 k4 v1
27 2021-05-14 k1-v1 8.0 k1 v1
30 2021-05-14 k1k3-v1 4.0 k1k3 v1
31 2021-05-31 k2-v1 2.0 k2 v1
32 2021-05-31 k2-v2 1.0 k2 v2
Pivot
要重塑并向列名称添加后缀 -last
的数据框
val v1-last v2-last
keys
k1 2021-05-14 2021-04-30
k1k3 2021-05-14 NaN
k2 2021-05-31 2021-05-31
k4 2021-04-30 2021-04-10
首先,定义一个函数来获取任何给定列名所需的日期:
def last_date(col):
idx = df[col].last_valid_index()
return df.loc[idx, 'date'] if idx is not None else np.nan
然后将键分组到字典中,并使用它来构建生成的数据框:
keys = {}
for col in df.columns[1:]:
key, vn = col.split('-')
keys.setdefault(key, [None]*2)[int(vn[-1])-1] = last_date(col)
pd.DataFrame.from_records([[k] + dt_list for k, dt_list in keys.items()], columns=['keys', 'v1-last', 'v2-last'])
这会产生所需的输出:
keys v1-last v2-last
0 k1 2021-05-14 2021-04-30
1 k2 2021-05-31 2021-05-31
2 k1k3 2021-05-14 NaN
3 k4 2021-04-30 2021-04-10
结合@MustafaAydin 和@ShubhamSharma 的想法,我们可以做到这一点:
temp = df.set_index('date')
应用pd.Series.last_valid_index
:
# you could use `agg` since it is a reducer
temp = temp.apply(pd.Series.last_valid_index)
将索引转换为 MultiIndex:
temp.index = temp.index.str.split("-", expand = True)
取消堆叠并添加后缀:
temp.unstack().add_suffix('_last')
v1_last v2_last
k1 2021-05-14 2021-04-30
k1k3 2021-05-14 None
k2 2021-05-31 2021-05-31
k4 2021-04-30 2021-04-10