使用 np.vectorize 在数据框中创建列

Using np.vectorize to create a column in a data frame

我有一个数据框,其中包含两列数字和第三列重复字母。让我们这样说:

import pandas as pd
import numpy as np

df = pd.DataFrame(np.random.randint(0,100,size=(100, 2)), columns=list('xy'))
letters = ['A', 'B', 'C', 'D'] * int(len(df.index) / 4)
df['letters'] = letters

我想创建两个新列,将 'x' 和 'y' 列中的数字与其对应字母的平均值进行比较。例如,一个新列将只包含数字 10(如果比平均值低 20% 或更好)、-10(如果比平均值低 20%)或者 0。

我写了下面的函数:

def scoreFunHigh(dataField, mean, diff, multip):

    upper = mean * (1 + diff)
    lower = mean * (1 - diff)

    if dataField > upper:
        return multip * 1
    elif dataField < lower:
        return multip * (-1)
    else:
        return 0

然后创建列如下:

letterMeanX = df.groupby('letters')['x'].transform(np.nanmean)
df['letter x score'] = np.vectorize(scoreFunHigh)(df['x'], letterMeanX, 0.2, 10)

letterMeanY = df.groupby('letters')['y'].transform(np.nanmean)
df['letter y score'] = np.vectorize(scoreFunHigh)(df['y'], letterMeanY, 0.3, 5)

这行得通。但是,我收到以下运行时警告:

C:\Users\ ... \Python\Python38\lib\site-packages\numpy\lib\function_base.py:2167: RuntimeWarning: 在中遇到无效值? (矢量化) 输出 = ufunc(*输入)

(请注意,如果我 运行 与上面的代码完全相同,我不会收到错误。我的真实数据框要大得多,我正在为不同的数据使用多个函数)

这里有什么问题?有没有更好的设置方法?

非常感谢

这里是不使用的版本 np.vectorize

def scoreFunHigh(val, mean, diff, multip):

    upper = mean * (1 + diff)
    lower = mean * (1 - diff)

    if val > upper:
        return multip * 1
    elif val < lower:
        return multip * (-1)
    else:
        return 0

letterMeanX = df.groupby('letters')['x'].apply(lambda x: np.nanmean(x))
df['letter x score'] =  df.apply(
    lambda row: scoreFunHigh(row['x'], letterMeanX[row['letters']], 0.2, 10), axis=1)

输出

     x   y letters  letter x score
0   52  76       A               0
1   90  99       B              10
2   87  43       C              10
3   44  73       D               0
4   49   3       A               0
..  ..  ..     ...             ...
95  16  51       D             -10
96  38   3       A               0
97  43  47       B               0
98  58  39       C               0
99  41  26       D               0

您提供的示例没有产生运行时警告,因此我们无法帮助您进行诊断。我不知道更完整的追溯是否提供了任何有用的信息。

但是让我们看看计算结果:

In [70]: np.vectorize(scoreFunHigh)(df['x'], letterMeanX, 0.2, 10)              
Out[70]: 
array([-10,   0,  10, -10,   0,   0, -10, -10,  10,   0,   0,  10, -10,
       -10,   0,  10,  10, -10,   0,  10, -10, -10, -10,  10,  10, -10,
       ...
       -10,  10, -10,   0,   0,  10,  10,   0,  10])

以及 df 赋值:

In [74]: df['letter x score'] = np.vectorize(scoreFunHigh)(df['x'], letterMeanX,
    ...:  0.2, 10) 
    ...:                                                                        
In [75]: df                                                                     
Out[75]: 
     x   y letters  letter x score
0   33  98       A             -10
1   38  49       B               0
2   78  46       C              10
3   31  46       D             -10
4   41  74       A               0
..  ..  ..     ...             ...
95  51   4       D               0
96  70   4       A              10
97  74  74       B              10
98  54  70       C               0
99  87  44       D              10

经常 np.vectorize 由于 otypes 问题而出现问题(阅读文档);如果试验计算产生一个整数,则 return dtype 设置为那个,如果其他值是浮点数,则会出现问题。但是在这种情况下,结果只能具有三个值之一,[-10,0,10](最后一个参数)。

您提供的警告表明较大数据框中的某些值对于您的 scoreFunHigh 函数中的计算是错误的。但是警告没有提供足够的细节来说明什么。

将真正的 numpy 向量化应用于此问题相对容易,因为它依赖于两个系列,df['x] 一个 letterMeanX 和 2 个标量。

In [111]: letterMeanX = df.groupby('letters')['x'].transform(np.nanmean)        
In [112]: letterMeanX.shape                                                     
Out[112]: (100,)
In [113]: df['x'].shape                                                         
Out[113]: (100,)
In [114]: upper = letterMeanX *(1+0.2)                                          
In [115]: lower = letterMeanX *(1-0.2)                                          
In [116]: res = np.zeros(letterMeanX.shape,int)                                 
In [117]: res[df['x']>upper] = 10                                               
In [118]: res[df['x']<lower] = -10                                              
In [119]: np.allclose(res, Out[70])                                             
Out[119]: True

换句话说,不是逐行应用 upper/lower 比较,而是将其应用于整个系列。它仍在迭代,但在编译的 numpy 方法中,速度要快得多。 np.vectorize 只是一个迭代的包装器。它仍然会为每一行调用一次 python 函数。希望性能免责声明足够清楚。

考虑直接调用您的函数,稍微调整方法以使用 numpy.select (or numpy.where) 处理条件逻辑。使用这种方法,没有循环 运行 而是对 Series 和标量参数的矢量化操作:

def scoreFunHigh(dataField, mean, diff, multip): 

   conds = [dataField > mean * (1 + diff),
            dataField < mean * (1 - diff)]

   vals = [multip * 1, multip * (-1)]

   return np.select(conds, vals, default=0)


letterMeanX = df.groupby('letters')['x'].transform(np.nanmean) 
df['letter x score'] = scoreFunHigh(df['x'], letterMeanX, 0.2, 10) 

letterMeanY = df.groupby('letters')['y'].transform(np.nanmean) 
df['letter y score'] = scoreFunHigh(df['y'], letterMeanY, 0.3, 5)