【发布时间】:2021-05-19 15:45:09
【问题描述】:
我有一个“基础”DataFrame a,其中包含一个标识符 seq 和连接键,以及一个将被合并的“值”DataFrame b:
import numpy as np
import pandas as pd
a = pd.DataFrame({
'id':range(11),
'seq':[
1, 1, 1, 2, 2, 3, 3, 3, 4, 5, 5],
'class':[
'alpha', 'beta', 'gaga', np.nan, np.nan, np.nan, np.nan, np.nan,
np.nan, 'beta', 'beta'],
'style':[
'x', 'x', 'x', 'y', 'y', np.nan, np.nan, np.nan, np.nan, np.nan,
np.nan],
'drama':[
'no', 'no', 'no', 'yes', 'oh yes', 'no', 'yes', 'oh yes', np.nan,
'yes', 'oh yes']})
b = pd.DataFrame({
'class':[
'gaga', 'alpha', 'alpha', 'alpha', 'alpha', 'alpha', 'beta', 'beta',
'alpha', 'gaga', 'beta', 'beta', 'alpha', 'beta', 'gaga', 'alpha',
'beta', 'alpha', 'beta', 'gaga', 'gaga', 'beta', 'beta', 'beta',
'gaga'],
'style':[
'y', 'y', 'y', 'x', 'x', 'x', 'y', 'x', 'x', 'x', 'y', 'y', 'y', 'y',
'x', 'y', 'y', 'y', 'y', 'x', 'x', 'x', 'y', 'x', 'y'],
'drama':[
'yes', 'no', 'no', 'no', 'no', 'oh yes', 'oh yes', 'oh yes', 'oh yes',
'no', 'yes', 'oh yes', 'no', 'no', 'yes', 'yes', 'no', 'yes', 'oh yes',
'oh yes', 'oh yes', 'no', 'oh yes', 'yes', 'yes'],
'start':[
838, 727, 700, 840, 530, 507, 871, 585, 120, 164, 562, 750, 953, 733,
337, 307, 277, 972, 3, 805, 539, 600, 8, 382, 147],
'end':[
198, 328, 591, 427, 151, 126, 132, 149, 856, 725, 608, 726, 178, 521,
316, 154, 633, 4, 113, 881, 258, 32, 354, 259, 958]})
我想做的是创建一个函数,该函数允许对b 进行某种“递归”连接,该函数将只连接每行可用的非空列。在a 的情况下:
- 其中
class、style和drama不为空,将加入三个键; - 如果
class为空,将仅加入style和drama; - 如果
class和style都为空,将仅在drama上加入; - 如果所有三列都为空,将在整个值 DataFrame
b上“加入”; - 空列不一定相同:例如,如果
style为空,它将加入class和drama。
就结果而言,在此示例中,输出将与手动执行不那么智能的操作相同:
ll = []
x, y, z, w, v = a.loc[:2], a.loc[3:4], a.loc[5:7], a.loc[8:8], a.loc[9:]
ll.append(x.merge(b, on=['class', 'style', 'drama']))
ll.append(y.drop(columns='class').merge(b, on=['style', 'drama']))
ll.append(z.drop(columns=['class', 'style']).merge(b, on='drama'))
ll.append(v.drop(columns='style').merge(b, on=['class', 'drama']))
# For all null values, get the entire DataFrame
w['placeholder'] = 1
b['placeholder'] = 1
ll.append(w
.drop(columns=['class', 'style', 'drama'])
.merge(b, on='placeholder')
.drop(columns='placeholder'))
result = pd.concat(ll)
但是,在这种情况下,手动操作是可能的,因为我事先已经知道如何隔离“组”(x、y、z、w 和 v)以及哪些列我将用于每个中的合并操作。
我用非常有限的可用性实现了一半,并且在我看来使用了一种低于标准的方式来处理列:
def recjoin(base: pd.DataFrame, other: pd.DataFrame, keys: list) -> pd.DataFrame:
missing_cols = base.columns[base.isnull().any()]
if len(missing_cols) == 0:
result = base.merge(other, on=idx)
else:
nonmissing = base.dropna(subset=keys)
result_nonmissing = nonmissing.merge(other, on=keys)
id_missing = base.index.difference(nonmissing.index)
missing = base.loc[id_missing].drop(columns=missing_cols)
if isinstance(keys, str):
keys = [keys]
alt_keys = list(pd.Index(keys).difference(missing_cols))
result_missing = missing.merge(other, on=alt_keys)
result = pd.concat([nonmissing, missing])
return result
这样,如果a.loc[:4] 被传递给base,它会起作用,但如果它是a.loc[:7],则不会,因为在第二种情况下,NaN 列的数量是可变的:
In [1]: a.loc[:4]
Out[1]:
id seq class style drama
0 0 1 alpha x no
1 1 1 beta x no
2 2 1 gaga x no
3 3 2 NaN y yes
4 4 2 NaN y oh yes
In [2]: a.loc[:7]
Out[2]:
id seq class style drama
0 0 1 alpha x no
1 1 1 beta x no
2 2 1 gaga x no
3 3 2 NaN y yes
4 4 2 NaN y oh yes
5 5 3 NaN NaN no
6 6 3 NaN NaN yes
7 7 3 NaN NaN oh yes
在这种情况下,最好的方法是什么,以免我们落入iterrows 解决方案?
【问题讨论】:
-
实际情况下两个数据框有多大?
-
a有大约 1000 行,而b通常有 600k 行。目前,我通过“糟糕”实现获得的最大输出是 360 万行,但平均约为 70 万行。