如何对DataFrame中的每一列使用一个Series进行操作?

66

问题

给定一个Series s 和一个 DataFrame df,我该如何对df的每一列使用s进行操作?

df = pd.DataFrame(
    [[1, 2, 3], [4, 5, 6]],
    index=[0, 1],
    columns=['a', 'b', 'c']
)

s = pd.Series([3, 14], index=[0, 1])

当我尝试添加它们时,我得到了全部的np.nan

df + s

    a   b   c   0   1
0 NaN NaN NaN NaN NaN
1 NaN NaN NaN NaN NaN

我原本以为我会得到的是

    a   b   c
0   4   5   6
1  18  19  20

目的和动机

我已经多次看到这种问题,并看到许多其他涉及此类元素的问题。 最近,在评论中寻找合适的规范问答时,我不得不花费一些时间解释这个概念。 我没有找到一个合适的,所以我想写一个。

这些问题通常与特定操作有关,但同样适用于大多数算术运算。

  • 如何从DataFrame的每列中减去一个Series
  • 如何在DataFrame的每列中添加一个Series
  • 如何将DataFrame的每列乘以一个Series
  • 如何将DataFrame的每列除以一个Series

这个回答解决了你的问题吗?如何在Pandas中将多列乘以一列 - rachwa
3个回答

78
创建一个关于SeriesDataFrame对象的心智模型会很有帮助。

Series 的解剖

Series 可以被视为增强版的字典。这并不总是一个完美的类比,但我们从这里开始。此外,您可以做出其他类比,但我选择字典来展示本文的目的。

index

这些是我们可以引用以获取相应值的键。当索引元素唯一时,与字典的比较非常接近。

values

这些是由索引键控的相应值。

DataFrame 的解剖

DataFrame 可以被视为 Series 字典或 Series 的字典。在这种情况下,键是列名,而值是作为 Series 对象的列本身。每个 Series 同意共享相同的 index,这是 DataFrame 的索引。

columns

这些是我们可以引用以获取相应 Series 的键。

index

这是所有 Series 值都同意共享的索引。

注意:关于 columnsindex 对象

它们是相同类型的对象。一个 DataFrameindex 可以用作另一个 DataFramecolumns。事实上,当您执行 df.T 以获取转置时,就会发生这种情况。

values

这是包含 DataFrame 中数据的二维数组。现实情况是,values 不是存储在 DataFrame 对象内部的内容。 (好吧,有时候是的,但我不打算尝试描述块管理器)。重点是,最好将其视为访问数据的二维数组。

定义示例数据

以下是可用作 SeriesDataFrameindex,或者可用作 DataFramecolumns 的示例 pandas.Index 对象:

idx_lower = pd.Index([*'abcde'], name='lower')
idx_range = pd.RangeIndex(5, name='range')

以下是使用上述 pandas.Index 对象的示例 pandas.Series 对象:

s0 = pd.Series(range(10, 15), idx_lower)
s1 = pd.Series(range(30, 40, 2), idx_lower)
s2 = pd.Series(range(50, 10, -8), idx_range)

这些是使用上述 pandas.Index 对象的示例 pandas.DataFrame 对象:
df0 = pd.DataFrame(100, index=idx_range, columns=idx_lower)
df1 = pd.DataFrame(
    np.arange(np.product(df0.shape)).reshape(df0.shape),
    index=idx_range, columns=idx_lower
)

Series on Series 系列

当对两个Series进行操作时,对齐是显而易见的。您将一个Seriesindex与另一个Seriesindex对齐。

s1 + s0

lower
a    40
b    43
c    46
d    49
e    52
dtype: int64

当我在进行操作之前随机洗牌一个数组时,这和不洗牌但在操作之前手动调整数组的索引顺序是等效的。索引仍然会对齐。

s1 + s0.sample(frac=1)

lower
a    40
b    43
c    46
d    49
e    52
dtype: int64

当我使用打乱后的Series的值时,情况是不一样的。在这种情况下,Pandas没有index可以对齐,因此会按照位置操作。

s1 + s0.sample(frac=1).values

lower
a    42
b    42
c    47
d    50
e    49
dtype: int64

添加一个标量

s1 + 1

lower
a    31
b    33
c    35
d    37
e    39
dtype: int64

DataFrameDataFrame 上的操作

当在两个DataFrame之间进行操作时,它们会自动对齐,执行我们所期望的操作:

df0 + df1

lower    a    b    c    d    e
range
0      100  101  102  103  104
1      105  106  107  108  109
2      110  111  112  113  114
3      115  116  117  118  119
4      120  121  122  123  124

它对第二个DataFrame在两个轴上进行了洗牌。 indexcolumns仍然对齐,给我们相同的结果。

df0 + df1.sample(frac=1).sample(frac=1, axis=1)

lower    a    b    c    d    e
range
0      100  101  102  103  104
1      105  106  107  108  109
2      110  111  112  113  114
3      115  116  117  118  119
4      120  121  122  123  124

这是相同的洗牌过程,但添加的是数组而不是DataFrame。它不再对齐,将得到不同的结果。

df0 + df1.sample(frac=1).sample(frac=1, axis=1).values

lower    a    b    c    d    e
range
0      123  124  121  122  120
1      118  119  116  117  115
2      108  109  106  107  105
3      103  104  101  102  100
4      113  114  111  112  110

添加一个一维数组。它将与列对齐并在行间广播。

df0 + [*range(2, df0.shape[1] + 2)]

lower    a    b    c    d    e
range
0      102  103  104  105  106
1      102  103  104  105  106
2      102  103  104  105  106
3      102  103  104  105  106
4      102  103  104  105  106

添加一个标量。由于没有与之对齐的内容,因此向所有内容广播:

df0 + 1

lower    a    b    c    d    e
range
0      101  101  101  101  101
1      101  101  101  101  101
2      101  101  101  101  101
3      101  101  101  101  101
4      101  101  101  101  101

Series上的DataFrame

如果将DataFrame视为Series字典,将Series视为值字典,则在DataFrameSeries之间进行操作时,它们应该通过它们的“键”进行对齐是非常自然的。

s0:
lower    a    b    c    d    e
        10   11   12   13   14

df0:
lower    a    b    c    d    e
range
0      100  100  100  100  100
1      100  100  100  100  100
2      100  100  100  100  100
3      100  100  100  100  100
4      100  100  100  100  100

当我们操作时,s0['a'] 中的 10 被加到整个 df0['a'] 列中:

df0 + s0

lower    a    b    c    d    e
range
0      110  111  112  113  114
1      110  111  112  113  114
2      110  111  112  113  114
3      110  111  112  113  114
4      110  111  112  113  114

本文的核心问题与重点

如果我需要获取s2df0怎么办?

s2:               df0:

             |    lower    a    b    c    d    e
range        |    range
0      50    |    0      100  100  100  100  100
1      42    |    1      100  100  100  100  100
2      34    |    2      100  100  100  100  100
3      26    |    3      100  100  100  100  100
4      18    |    4      100  100  100  100  100

在操作时,我得到了问题中提到的所有 np.nan

df0 + s2

        a   b   c   d   e   0   1   2   3   4
range
0     NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1     NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2     NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
3     NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4     NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

这并不是我们想要的结果,因为Pandas会将s2indexdf0columns进行对齐。结果的columns包括了s2indexdf0columns的合集。

我们可以通过巧妙地转置来模拟它:

(df0.T + s2).T

lower    a    b    c    d    e
range
0      150  150  150  150  150
1      142  142  142  142  142
2      134  134  134  134  134
3      126  126  126  126  126
4      118  118  118  118  118

但事实证明,Pandas有更好的解决方案。有一些操作方法可以允许我们传递一个axis参数来指定要与之对齐的轴。

- sub + add * mul / div ** pow

因此,答案就是:

df0.add(s2, axis='index')

lower    a    b    c    d    e
range
0      150  150  150  150  150
1      142  142  142  142  142
2      134  134  134  134  134
3      126  126  126  126  126
4      118  118  118  118  118

事实证明,axis='index'axis=0是同义词。 同样地,axis='columns'axis=1是同义词:

df0.add(s2, axis=0)

lower    a    b    c    d    e
range
0      150  150  150  150  150
1      142  142  142  142  142
2      134  134  134  134  134
3      126  126  126  126  126
4      118  118  118  118  118

其他操作

df0.sub(s2, axis=0)

lower   a   b   c   d   e
range
0      50  50  50  50  50
1      58  58  58  58  58
2      66  66  66  66  66
3      74  74  74  74  74
4      82  82  82  82  82

df0.mul(s2, axis=0)

lower     a     b     c     d     e
range
0      5000  5000  5000  5000  5000
1      4200  4200  4200  4200  4200
2      3400  3400  3400  3400  3400
3      2600  2600  2600  2600  2600
4      1800  1800  1800  1800  1800

df0.div(s2, axis=0)

lower         a         b         c         d         e
range
0      2.000000  2.000000  2.000000  2.000000  2.000000
1      2.380952  2.380952  2.380952  2.380952  2.380952
2      2.941176  2.941176  2.941176  2.941176  2.941176
3      3.846154  3.846154  3.846154  3.846154  3.846154
4      5.555556  5.555556  5.555556  5.555556  5.555556

df0.pow(1 / s2, axis=0)

lower         a         b         c         d         e
range
0      1.096478  1.096478  1.096478  1.096478  1.096478
1      1.115884  1.115884  1.115884  1.115884  1.115884
2      1.145048  1.145048  1.145048  1.145048  1.145048
3      1.193777  1.193777  1.193777  1.193777  1.193777
4      1.291550  1.291550  1.291550  1.291550  1.291550

首先,需要解释一些较高层次的概念。由于我的动机是分享知识和教授技能,因此我希望尽可能清晰易懂。


3
另一个对我来说很好的资源,可以为未来的问题做个标记。 :-) - BENY
另一种方法是通过广播 df[df.columns] = df.values+s.values[:,None] - Bharath M Shetty

11

我更倾向于 piSquared提到的 方法(即 df.add(s, axis=0)),但另一种方法使用 applylambda 一起在数据帧中对每列执行操作:

>>>> df.apply(lambda col: col + s)
    a   b   c
0   4   5   6
1  18  19  20

要将lambda函数应用于行,请使用axis=1

>>> df.T.apply(lambda row: row + s, axis=1)
   0   1
a  4  18
b  5  19
c  6  20

当转换更加复杂时,例如:

df.apply(lambda col: 0.5 * col ** 2 + 2 * s - 3)

基本上,如果我没记错的话,你可以在第一段代码末尾简单地添加.T,而不是使用axis=1 - Bharath M Shetty

1

根据我的经验,我想再增加一层解释。这是之前其他人所做的工作的延伸。本文展示了如何在保留额外列的值的情况下操作带有DataFrameSeries。以下是该过程的简短演示。

import pandas as pd

d = [1.056323, 0.126681, 
     0.142588, 0.254143,
     0.15561, 0.139571,
     0.102893, 0.052411]
     
df = pd.Series(d, index = ['const', '426', '428', '424', '425', '423', '427', '636'])

print(df)
const    1.056323
426      0.126681
428      0.142588
424      0.254143
425      0.155610
423      0.139571
427      0.102893
636      0.052411

d2 = {
'loc': ['D', 'D', 'E', 'E', 'F', 'F', 'G', 'G', 'E', 'D'],
'426': [9, 2, 3, 2, 4, 0, 2, 7, 2, 8],
'428': [2, 4, 1, 0, 2, 1, 3, 0, 7, 8],
'424': [1, 10, 5, 8, 2, 7, 10, 0, 3, 5],
'425': [9, 2, 6, 8, 9, 1, 7, 3, 8, 6],
'423': [4, 2, 8, 7, 9, 6, 10, 5, 9, 9],
'423': [2, 7, 3, 10, 8, 1, 2, 9, 3, 9],
'427': [4, 10, 4, 0, 8, 3, 1, 5, 7, 7],
'636': [10, 5, 6, 4, 0, 5, 1, 1, 4, 8],
'seq': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
}

df2 = pd.DataFrame(d2)

print(df2)
  loc  426  428  424  425  423  427  636  seq
0   D    9    2    1    9    2    4   10    1
1   D    2    4   10    2    7   10    5    1
2   E    3    1    5    6    3    4    6    1
3   E    2    0    8    8   10    0    4    1
4   F    4    2    2    9    8    8    0    1
5   F    0    1    7    1    1    3    5    1
6   G    2    3   10    7    2    1    1    1
7   G    7    0    0    3    9    5    1    1
8   E    2    7    3    8    3    7    4    1
9   D    8    8    5    6    9    7    8    1

DataFrame 乘以 Series 并保留不同的列

  1. 创建一个要操作的 DataFrameSeries 中元素的列表:
col = ['426', '428', '424', '425', '423', '427', '636']

使用列表执行您的操作,并指定要使用的轴:
df2[col] = df2[col].mul(df[col], axis=1)

print(df2)
  loc       426       428       424      425       423       427       636  seq
0   D  1.140129  0.285176  0.254143  1.40049  0.279142  0.411572  0.524110    1
1   D  0.253362  0.570352  2.541430  0.31122  0.976997  1.028930  0.262055    1
2   E  0.380043  0.142588  1.270715  0.93366  0.418713  0.411572  0.314466    1
3   E  0.253362  0.000000  2.033144  1.24488  1.395710  0.000000  0.209644    1
4   F  0.506724  0.285176  0.508286  1.40049  1.116568  0.823144  0.000000    1
5   F  0.000000  0.142588  1.779001  0.15561  0.139571  0.308679  0.262055    1
6   G  0.253362  0.427764  2.541430  1.08927  0.279142  0.102893  0.052411    1
7   G  0.886767  0.000000  0.000000  0.46683  1.256139  0.514465  0.052411    1
8   E  0.253362  0.998116  0.762429  1.24488  0.418713  0.720251  0.209644    1
9   D  1.013448  1.140704  1.270715  0.93366  1.256139  0.720251  0.419288    1

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