首页 文章

将pandas函数应用于列以创建多个新列?

提问于
浏览
130

如何在熊猫中做到这一点:

我在单个文本列上有一个函数 extract_text_features ,返回多个输出列 . 具体来说,该函数返回6个值 .

该函数有效,但似乎没有任何正确的返回类型(pandas DataFrame / numpy数组/ Python列表),以便输出可以正确分配 df.ix[: ,10:16] = df.textcol.map(extract_text_features)

所以我认为我需要回到 df.iterrows() 迭代,根据this

更新:使用 df.iterrows() 进行迭代的速度至少要慢20倍,因此我将函数放弃并将函数拆分为六个不同的 .map(lambda ...) 调用 .

11 回答

  • 63

    Build 用户1827356的答案,你可以使用 df.merge 一次完成作业:

    df.merge(df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1})), 
        left_index=True, right_index=True)
    
        textcol  feature1  feature2
    0  0.772692  1.772692 -0.227308
    1  0.857210  1.857210 -0.142790
    2  0.065639  1.065639 -0.934361
    3  0.819160  1.819160 -0.180840
    4  0.088212  1.088212 -0.911788
    
  • 37

    我通常使用 zip 执行此操作:

    >>> df = pd.DataFrame([[i] for i in range(10)], columns=['num'])
    >>> df
        num
    0    0
    1    1
    2    2
    3    3
    4    4
    5    5
    6    6
    7    7
    8    8
    9    9
    
    >>> def powers(x):
    >>>     return x, x**2, x**3, x**4, x**5, x**6
    
    >>> df['p1'], df['p2'], df['p3'], df['p4'], df['p5'], df['p6'] = \
    >>>     zip(*df['num'].map(powers))
    
    >>> df
            num     p1      p2      p3      p4      p5      p6
    0       0       0       0       0       0       0       0
    1       1       1       1       1       1       1       1
    2       2       2       4       8       16      32      64
    3       3       3       9       27      81      243     729
    4       4       4       16      64      256     1024    4096
    5       5       5       25      125     625     3125    15625
    6       6       6       36      216     1296    7776    46656
    7       7       7       49      343     2401    16807   117649
    8       8       8       64      512     4096    32768   262144
    9       9       9       81      729     6561    59049   531441
    
  • 113

    这就是我过去所做的

    df = pd.DataFrame({'textcol' : np.random.rand(5)})
    
    df
        textcol
    0  0.626524
    1  0.119967
    2  0.803650
    3  0.100880
    4  0.017859
    
    df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1}))
       feature1  feature2
    0  1.626524 -0.373476
    1  1.119967 -0.880033
    2  1.803650 -0.196350
    3  1.100880 -0.899120
    4  1.017859 -0.982141
    

    编辑完整性

    pd.concat([df, df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1}))], axis=1)
        textcol feature1  feature2
    0  0.626524 1.626524 -0.373476
    1  0.119967 1.119967 -0.880033
    2  0.803650 1.803650 -0.196350
    3  0.100880 1.100880 -0.899120
    4  0.017859 1.017859 -0.982141
    
  • 6

    对于95%的用例,这是实现此目的的正确和最简单的方法:

    >>> df = pd.DataFrame(zip(*[range(10)]), columns=['num'])
    >>> df
        num
    0    0
    1    1
    2    2
    3    3
    4    4
    5    5
    
    >>> def example(x):
    ...     x['p1'] = x['num']**2
    ...     x['p2'] = x['num']**3
    ...     x['p3'] = x['num']**4
    ...     return x
    
    >>> df = df.apply(example, axis=1)
    >>> df
        num  p1  p2  p3
    0    0   0   0    0
    1    1   1   1    1
    2    2   4   8   16
    3    3   9  27   81
    4    4  16  64  256
    
  • 5

    Summary: 如果您只想创建几列,请使用 df[['new_col1','new_col2']] = df[['data1','data2']].apply( function_of_your_choosing(x), axis=1)

    对于此解决方案,您创建的新列的数量必须等于您用作.apply()函数输入的列数 . 如果您想做其他事情,请查看其他答案 .

    Details 让's say you have two-column dataframe. The first column is a person'的高度为10;第二个是20岁时的人的身高 .

    假设你需要计算每个人身高的平均值和每个人身高的总和 . 这是每行两个值 .

    您可以通过以下即将应用的功能执行此操作:

    def mean_and_sum(x):
        """
        Calculates the mean and sum of two heights.
        Parameters:
        :x -- the values in the row this function is applied to. Could also work on a list or a tuple.
        """
    
        sum=x[0]+x[1]
        mean=sum/2
        return [mean,sum]
    

    你可能会像这样使用这个函数:

    df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1)
    

    (要明确:此apply函数接受子集化数据框中每一行的值并返回一个列表 . )

    但是,如果你这样做:

    df['Mean_&_Sum'] = df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1)
    

    您将创建一个包含[mean,sum]列表的新列,您可能希望避免这些列,因为这需要另一个Lambda / Apply .

    相反,您希望将每个值分解为自己的列 . 为此,您可以一次创建两列:

    df[['Mean','Sum']] = df[['height_at_age_10','height_at_age_20']]
    .apply(mean_and_sum(x),axis=1)
    
  • 0

    我看了几种方法,这里显示的方法(返回一个熊猫系列)似乎并不是最有效的 .

    如果我们从一个庞大的随机数据数据框开始:

    # Setup a dataframe of random numbers and create a 
    df = pd.DataFrame(np.random.randn(10000,3),columns=list('ABC'))
    df['D'] = df.apply(lambda r: ':'.join(map(str, (r.A, r.B, r.C))), axis=1)
    columns = 'new_a', 'new_b', 'new_c'
    

    此处显示的示例:

    # Create the dataframe by returning a series
    def method_b(v):
        return pd.Series({k: v for k, v in zip(columns, v.split(':'))})
    %timeit -n10 -r3 df.D.apply(method_b)
    

    10个循环,最好为3:每循环2.77秒

    另一种方法:

    # Create a dataframe from a series of tuples
    def method_a(v):
        return v.split(':')
    %timeit -n10 -r3 pd.DataFrame(df.D.apply(method_a).tolist(), columns=columns)
    

    10个循环,最佳3:每循环8.85毫秒

    通过我的计算,采用一系列元组然后将其转换为DataFrame效率更高 . 如果我的工作中出现错误,我会有兴趣听到别人的想法 .

  • 7

    对于大量数据,接受的解决方案将非常缓慢 . 具有最多数量的upvotes的解决方案有点难以阅读,并且还因数字数据而变慢 . 如果每个新列可以独立于其他列计算,我只需直接分配它们而不使用 apply .

    假字符数据示例

    在DataFrame中创建100,000个字符串

    df = pd.DataFrame(np.random.choice(['he jumped', 'she ran', 'they hiked'],
                                       size=100000, replace=True),
                      columns=['words'])
    df.head()
            words
    0     she ran
    1     she ran
    2  they hiked
    3  they hiked
    4  they hiked
    

    假设我们想要在原始问题中提取一些文本特征 . 例如,让我们提取第一个字符,计算字母“e”的出现次数并将该短语大写 .

    df['first'] = df['words'].str[0]
    df['count_e'] = df['words'].str.count('e')
    df['cap'] = df['words'].str.capitalize()
    df.head()
            words first  count_e         cap
    0     she ran     s        1     She ran
    1     she ran     s        1     She ran
    2  they hiked     t        2  They hiked
    3  they hiked     t        2  They hiked
    4  they hiked     t        2  They hiked
    

    Timings

    %%timeit
    df['first'] = df['words'].str[0]
    df['count_e'] = df['words'].str.count('e')
    df['cap'] = df['words'].str.capitalize()
    127 ms ± 585 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
    
    def extract_text_features(x):
        return x[0], x.count('e'), x.capitalize()
    
    %timeit df['first'], df['count_e'], df['cap'] = zip(*df['words'].apply(extract_text_features))
    101 ms ± 2.96 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    

    令人惊讶的是,您可以通过循环遍历每个值来获得更好的性能

    %%timeit
    a,b,c = [], [], []
    for s in df['words']:
        a.append(s[0]), b.append(s.count('e')), c.append(s.capitalize())
    
    df['first'] = a
    df['count_e'] = b
    df['cap'] = c
    79.1 ms ± 294 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
    

    假数字数据的另一个例子

    创建100万个随机数并从上面测试 powers 函数 .

    df = pd.DataFrame(np.random.rand(1000000), columns=['num'])
    
    
    def powers(x):
        return x, x**2, x**3, x**4, x**5, x**6
    
    %%timeit
    df['p1'], df['p2'], df['p3'], df['p4'], df['p5'], df['p6'] = \
           zip(*df['num'].map(powers))
    1.35 s ± 83.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    分配每列快25倍且非常易读:

    %%timeit 
    df['p1'] = df['num'] ** 1
    df['p2'] = df['num'] ** 2
    df['p3'] = df['num'] ** 3
    df['p4'] = df['num'] ** 4
    df['p5'] = df['num'] ** 5
    df['p6'] = df['num'] ** 6
    51.6 ms ± 1.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    

    我用more details here做了类似的回答,为什么 apply 通常不是那种方式 .

  • 2

    在2018年,我使用 apply() 与参数 result_type='expand'

    >>> appiled_df = df.apply(lambda row: fn(row.text), axis='columns', result_type='expand')
    >>> df = pd.concat([df, appiled_df], axis='columns')
    
  • 12

    在其他两个类似问题中发布了相同的答案 . 我喜欢这样做的方法是将函数的返回值包装在一个系列中:

    def f(x):
        return pd.Series([x**2, x**3])
    

    然后使用apply如下创建单独的列:

    df[['x**2','x**3']] = df.apply(lambda row: f(row['x']), axis=1)
    
  • 73

    您可以返回整行而不是值:

    df = df.apply(extract_text_features,axis = 1)
    

    函数返回行的位置

    def extract_text_features(row):
          row['new_col1'] = value1
          row['new_col2'] = value2
          return row
    
  • 1

    对我来说,这工作:

    输入df

    df = pd.DataFrame({'col x': [1,2,3]})
       col x
    0      1
    1      2
    2      3
    

    功能

    def f(x):
        return pd.Series([x*x, x*x*x])
    

    创建2个新列:

    df[['square x', 'cube x']] = df['col x'].apply(f)
    

    输出:

    col x  square x  cube x
    0      1         1       1
    1      2         4       8
    2      3         9      27
    

相关问题