df.apply 输出的新列中作为参数的特定熊猫列

Posted

技术标签:

【中文标题】df.apply 输出的新列中作为参数的特定熊猫列【英文标题】:Specific pandas columns as arguments in new column of df.apply outputs 【发布时间】:2018-03-03 03:01:02 【问题描述】:

给定一个熊猫数据框如下:

import pandas as pd
from sklearn.metrics import mean_squared_error

    df = pd.DataFrame.from_dict(  
         'row': ['a','b','c','d','e','y'],
            'a': [ 0, -.8,-.6,-.3, .8, .01],
            'b': [-.8,  0, .5, .7,-.9, .01],
            'c': [-.6, .5,  0, .3, .1, .01],
            'd': [-.3, .7, .3,  0, .2, .01],
            'e': [ .8,-.9, .1, .2,  0, .01],
            'y': [ .01, .01, .01, .01,  .01, 0],
       ).set_index('row')
df.columns.names = ['col']

我想使用参数的特定列创建一个新的 RMSE 值列(来自scikit-learn)。即,y_true = df['a','b','c']y_pred = df['x','y','x'] 的列。使用迭代方法很容易做到这一点:

for tup in df.itertuples():
    df.at[tup[0], 'rmse']  = mean_squared_error(tup[1:4], tup[4:7])**0.5

这给出了预期的结果:

col     a     b     c     d     e     y      rmse
row                                              
a    0.00 -0.80 -0.60 -0.30  0.80  0.01  1.003677
b   -0.80  0.00  0.50  0.70 -0.90  0.01  1.048825
c   -0.60  0.50  0.00  0.30  0.10  0.01  0.568653
d   -0.30  0.70  0.30  0.00  0.20  0.01  0.375988
e    0.80 -0.90  0.10  0.20  0.00  0.01  0.626658
y    0.01  0.01  0.01  0.01  0.01  0.00  0.005774

但我想要一个性能更高的解决方案,可能使用矢量化,因为我的数据框具有形状 (180000000, 52)。我也不喜欢按元组位置而不是按列名进行索引。下面的尝试:

df['rmse'] = df.apply(mean_squared_error(df[['a','b','c']], df[['d','e','y']])**0.5, axis=1)

得到错误:

TypeError: ("'numpy.float64' object is not callable", 'occurred at index a')

那么我在使用df.apply() 时做错了什么?这甚至会在迭代过程中最大限度地提高性能吗?

测试性能

我已经使用以下测试 df 测试了前两个受访者中每一个的上墙时间:

# set up test df
dim_x, dim_y = 50, 1000000
cols = ["a_"+str(i) for i in range(1,(dim_x//2)+1)]
cols_b = ["b_"+str(i) for i in range(1,(dim_x//2)+1)]
cols.extend(cols_b)
shuffle(cols)
df = pd.DataFrame(np.random.uniform(0,10,[dim_y, dim_x]), columns=cols)  #, index=idx, columns=cols
a = df.values

# define column samples
def column_index(df, query_cols):
    cols = df.columns.values
    sidx = np.argsort(cols)
    return sidx[np.searchsorted(cols,query_cols,sorter=sidx)]

c0 = [s for s in cols if "a" in s]
c1 = [s for s in cols if "b" in s]
s0 = a[:,column_index(df, c0)]
s1 = a[:,column_index(df, c1)]

结果如下:

%%time
# approach 1 - divakar
rmse_out = np.sqrt(((s0 - s1)**2).mean(1))
df['rmse_out'] = rmse_out

Wall time: 393 ms

%%time
# approach 2 - divakar
diffs = s0 - s1
rmse_out = np.sqrt(np.einsum('ij,ij->i',diffs,diffs)/3.0)
df['rmse_out'] = rmse_out

Wall time: 228 ms

%%time
# approach 3 - divakar
diffs = s0 - s1
rmse_out = np.sqrt((np.einsum('ij,ij->i',s0,s0) + \
         np.einsum('ij,ij->i',s1,s1) - \
       2*np.einsum('ij,ij->i',s0,s1))/3.0)
df['rmse_out'] = rmse_out

Wall time: 421 ms

使用 apply 函数的解决方案在几分钟后仍在运行...

【问题讨论】:

【参考方案1】:

方法#1

一种提高性能的方法是将底层数组数据与 NumPy ufunc 一起使用,同时对这两个列块进行切片以便以矢量化方式使用这些 ufunc,就像这样 -

a = df.values
rmse_out = np.sqrt(((a[:,0:3] - a[:,3:6])**2).mean(1))
df['rmse_out'] = rmse_out

方法 #2

np.einsum 替换squared-summation 来计算RMSE 值的另一种更快的方法-

diffs = a[:,0:3] - a[:,3:6]
rmse_out = np.sqrt(np.einsum('ij,ij->i',diffs,diffs)/3.0)

方法#3

使用公式计算rmse_out 的另一种方法:

(a - b)^2 = a^2 + b^2 - 2ab

将是提取切片:

s0 = a[:,0:3]
s1 = a[:,3:6]

那么,rmse_out 将是 -

np.sqrt(((s0**2).sum(1) + (s1**2).sum(1) - (2*s0*s1).sum(1))/3.0)

einsum 变成 -

np.sqrt((np.einsum('ij,ij->i',s0,s0) + \
         np.einsum('ij,ij->i',s1,s1) - \
       2*np.einsum('ij,ij->i',s0,s1))/3.0)

获取相应的列索引

如果您不确定a,b,.. 列是否按该顺序排列,我们可以使用column_index 找到这些索引。

因此,a[:,0:3] 将被 a[:,column_index(df, ['a','b','c'])] 替换,a[:,3:6] 将被 a[:,column_index(df, ['d','e','y'])] 替换。

【讨论】:

感谢您的解决方案!我对您的三个解决方案进行了墙上时间比较,并将其添加到我的原始帖子中。 @ThomasMatthew 太棒了!很高兴看到这些时间。 在较大的 DF 尺寸下获得 MemoryError,但在等效于 a[:,column_index(df, ['d','e','y'])] 时会失败。是否有另一种方法可以直接从数据帧中完成方法 2,而不是使用 a=df.values 创建值的副本,这可能会占用比需要更多的内存? @ThomasMatthew 对于输入数据帧中的所有浮点值,df.values 只是输入数据帧的视图,而不是副本。另外让我问你 - cols - a,b,c 是否总是列号 0,1,2d,e,y as 3,4,5 使用从 0 开始的索引? @Divakar 是的,我可以提前知道列号,它们应该始终基于 0 的索引【参考方案2】:

df.apply 方法:

df['rmse'] = df.apply(lambda x: mean_squared_error(x[['a','b','c']], x[['d','e','y']])**0.5, axis=1)

col     a     b     c     d     e     y      rmse
row                                              
a    0.00 -0.80 -0.60 -0.30  0.80  0.01  1.003677
b   -0.80  0.00  0.50  0.70 -0.90  0.01  1.048825
c   -0.60  0.50  0.00  0.30  0.10  0.01  0.568653
d   -0.30  0.70  0.30  0.00  0.20  0.01  0.375988
e    0.80 -0.90  0.10  0.20  0.00  0.01  0.626658
y    0.01  0.01  0.01  0.01  0.01  0.00  0.005774

【讨论】:

以上是关于df.apply 输出的新列中作为参数的特定熊猫列的主要内容,如果未能解决你的问题,请参考以下文章

如何有条件地将子字符串复制到熊猫数据框的新列中?

熊猫:追加行小计的新列

如何根据熊猫中其他列的条件创建新列

熊猫数据框条件 .mean() 取决于特定列中的值

将函数应用于两列并将输出映射到新列[重复]

计算熊猫列中的重复次数[重复]