用Python Pandas将动态列合并,并去除重复项

3
我们有一个使用场景,需要通过删除重复项来联接一行中的所有列值。数据存储在Pandas的DataFrame中。 例如,考虑以下具有列A、B、C的DataFrame df:
A   B   C   
X1  AX  X1
X2  X2  X1
X3  X3  X3
X4  XX  XX

我想添加一个新的列,将A、B和C连接起来,并按照顺序去除重复项。输出的结果应如下所示:
A   B   C   Newcol

X1  AX  X1  X1_AX
X2  X2  X1  X2_X1
X3  X3  X3  X3
X4  XX  XX  X4_XX

请注意,列数是动态的。目前我正在使用以下命令进行操作:
df.apply(lambda x: '-'.join(x.dropna().astype(str).drop_duplicates()),axis=1)

但是这个方法非常慢,对于我的数据需要大约150秒的时间。 但是由于数据框通常只有2列的情况占90%,因此我在代码中加入了一个if语句,并对只有2列的情况运行以下命令:

t1=pd.Series(np.where(df.iloc[:,0].dropna().astype(str) != df.iloc[:,1].dropna().astype(str), df.iloc[:,0].dropna().astype(str)+"-"+df.iloc[:,1].dropna().astype(str),df.iloc[:,1].dropna().astype(str)))

需要花费约55.3毫秒的时间

甚至可以

t1=df.iloc[:,0].dropna().astype(str).where(df.iloc[:,0].dropna().astype(str) == df.iloc[:,1].dropna().astype(str), df.iloc[:,0].dropna().astype(str)+"-"+df.iloc[:,1].dropna().astype(str))

两个操作的时间几乎相同(55毫秒对比150秒),但问题在于它仅适用于2列。我想创建一个通用语句,以便它可以处理n个列。我尝试使用reduce,但当我尝试使用3列时出现错误。

reduce((lambda x,y:pd.Series(np.where(df.iloc[:,x].dropna().astype(str) != df.iloc[:,y].dropna().astype(str), df.iloc[:,x].dropna().astype(str)+"-"+df.iloc[:,y].dropna().astype(str),df.iloc[:,y].dropna().astype(str)))),list(range(df.shape[1])))

类型错误:'str'和'int'的实例之间不支持'>='

请注意,df实际上是一个多核并行任务的一部分。因此,如果建议不包括并行性,那将是很好的。

2个回答

3

尝试

df['new'] = df.astype('str').apply(lambda x: '_'.join(set(x)), axis = 1)

    A   B   C   new
0   X1  AX  X1  AX_X1
1   X2  X2  X1  X1_X2
2   X3  X3  X3  X3
3   X4  XX  XX  X4_XX

编辑:维护列值的顺序

def my_append(x):
    l = []
    for elm in x:
        if elm not in l:
            l.append(elm)
    return '_'.join(l)


df['New col']=df.astype('str').apply(my_append, axis = 1)

1000 loops, best of 3: 871 µs per loop

返回

    A   B   C   New col
0   X1  AX  X1  X1_AX
1   X2  X2  X1  X2_X1
2   X3  X3  X3  X3
3   X4  XX  XX  X4_XX

编辑1: 如果您的某一列中有nan值,就像这样

    A   B   C
0   X1  AX  X1
1   X2  X2  X1
2   X3  X3  X3
3   NaN XX  XX

在函数中处理这个问题,然后应用。
def my_append(x):
l = []
for elm in x:
    if elm not in l:
        l.append(elm)
l = [x for x in l if str(x) != 'nan']
return '_'.join(l)

df['New col']=df.astype('str').apply(my_append, axis = 1)


    A   B   C   New col
0   X1  AX  X1  X1_AX
1   X2  X2  X1  X2_X1
2   X3  X3  X3  X3
3   NaN XX  XX  XX

抱歉,但正如我所提到的,我需要保留顺序。在设置中设置key.index会出错,并且也没有太多时间上的好处。 - niths4u
哇,这确实起作用了,新代码只需要2秒,而不是150秒。谢谢。有一个疑问,dropna()怎么样?难道不应该加上吗? - niths4u
为了避免“nan”成为值之一,我使用了df.fillna('').astype('str').apply(hutils.dup_remove, axis = 1)。在if语句中添加了“and elm != ''”。 - niths4u
那个方法可以运行,但是如果你比较时间的话,使用列表处理需要每次循环 887 微秒,而使用 fillna('') 的解决方案则需要 1.83 毫秒每次循环。 - Vaishali

1

pd.unique 不会排序。使用推导式来包装它。

df.assign(new_col=['_'.join(pd.unique(row)) for row in df.values])

    A   B   C new_col
0  X1  AX  X1   X1_AX
1  X2  X2  X1   X2_X1
2  X3  X3  X3      X3
3  X4  XX  XX   X4_XX

处理NaN
df.assing(new_col=[
        '_'.join(pd.unique([i for i in row if pd.notnull(i)])) for row in df.values
    ])

它无法处理NaN。 - niths4u
现在它可以工作了。谢谢。 %timeit 大约花费了2.71秒。 - niths4u

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接