如何计算pandas数据框中选定列的加权和?推荐的方法是什么?

5
例如,我想计算下面矩阵中列'a'和'c'的加权和,权重在字典w中定义。
df = pd.DataFrame({'a': [1,2,3], 
                   'b': [10,20,30], 
                   'c': [100,200,300],
                   'd': [1000,2000,3000]})
w = {'a': 1000., 'c': 10.}

我自己找到了一些选项(见下文),但都有点复杂。难道没有一个直接的pandas操作来处理这种基本用例吗?例如df.wsum(w)
我尝试了pd.DataFrame.dot,但它会引发值错误:
df.dot(pd.Series(w))
# This raises an exception:
# "ValueError: matrices are not aligned"

可以通过为每列指定权重来避免异常,但这不是我想要的。

w = {'a': 1000., 'b': 0., 'c': 10., 'd': 0. }
df.dot(pd.Series(w)) # This works

如何仅在列的子集上计算点积?另外,也可以在执行点操作之前选择感兴趣的列,或者利用pandas / numpy忽略计算(行向)和时值为nan的事实(请参见下面)。以下是我自己发现的三种方法:

w = {'a': 1000., 'c': 10.}

# 1) Create a complete lookup W.
W = { c: 0. for c in df.columns }
W.update(w)
ret = df.dot(pd.Series(W))

# 2) Select columns of interest before applying the dot product.
ret = df[list(w.keys())].dot(pd.Series(w))

# 3) Exploit the handling of NaNs when computing the (row-wise) sum
ret = (df * pd.Series(w)).sum(axis=1)
# (df * pd.Series(w)) contains columns full of nans

我是否错过了一个选项?

1
不比你的第三个选项好多少,但是... df.mul(w).sum(axis=1) - ayhan
你可以分享一下,你当前解决问题的三种方法中,哪些你不喜欢的地方? - gosuto
你可以这样做:df.loc[:, w].dot(pd.Series(w)) - Dani Mesejo
@jorijnsmit 我想知道是否错过了最佳选项。对一组列进行加权组合很常见,而且根据经验,pandas通常为这些常见任务提供简单的解决方案。我也可以想象我的三个建议在成本方面并不相等。最后,我没有在SO上找到一个令人满意的答案来解决我的问题,这就是为什么我收集了我目前的理解并请求澄清。也许其他人也会从中受益。 - normanius
4个回答

4
您可以像第一个示例那样使用 Series,只需在之后使用 reindex:
import pandas as pd

df = pd.DataFrame({'a': [1,2,3],
                   'b': [10,20,30],
                   'c': [100,200,300],
                   'd': [1000,2000,3000]})

w = {'a': 1000., 'c': 10.}
print(df.dot(pd.Series(w).reindex(df.columns, fill_value=0)))

输出

0    2000.0
1    4000.0
2    6000.0
dtype: float64

1
根据我的基准测试(请参见我的答案),你的解决方案对于非对齐权重向量 w 是最快的。因此,我接受了你的答案。 - normanius

3
这里有一个选项,无需创建 pd.Series :
(df.loc[:,w.keys()] * list(w.values())).sum(axis=1)
0    2000.0
1    4000.0
2    6000.0

从我所搜索的来看,在旧版本中顺序也得到了保留。 - yatu
1
好的回答,加一分! - Dani Mesejo
1
你也可以这样做:df.loc[:, w].dot(pd.Series(w)) - Dani Mesejo
感谢 @DanielMesejo :) - yatu

2

我再次遇到了自己的问题,并对现有答案进行了基准测试。

观察结果:首先用零填充不完整的权重向量是值得的,而不是首先查看列并将结果子框架点乘。


import pandas as pd
import numpy as np

def benchmark(n_rows, n_cols, n_ws):
    print("n_rows:%d, n_cols:%d, n_ws:%d" % (n_rows, n_cols, n_ws))
    df = pd.DataFrame(np.random.randn(n_rows, n_cols), 
                      columns=range(n_cols))
    w = dict(zip(np.random.choice(np.arange(n_cols), n_ws), 
                 np.random.randn(n_ws)))
    w0 = pd.Series(w).reindex(df.columns, fill_value=0).values

    # Method 0 (aligned vector w0, reference!)
    def fun0(df, w0): return df.values.dot(w0)
    # Method 1 (reindex)
    def fun1(df, w): return df.dot(pd.Series(w).reindex(df.columns, fill_value=0))
    # Method 2 (column view)
    def fun2(df, w): return (df.loc[:,w.keys()] * list(w.values())).sum(axis=1)
    # Method 3 (column view, faster)
    def fun3(df, w): return df.loc[:, w].dot(pd.Series(w))
    # Method 4 (column view, numpy)
    def fun4(df, w): return df[list(w.keys())].values.dot(list(w.values()))

    # Assert equivalence
    np.testing.assert_array_almost_equal(fun0(df,w0), fun1(df,w), decimal=10)
    np.testing.assert_array_almost_equal(fun0(df,w0), fun2(df,w), decimal=10)
    np.testing.assert_array_almost_equal(fun0(df,w0), fun3(df,w), decimal=10)
    np.testing.assert_array_almost_equal(fun0(df,w0), fun4(df,w), decimal=10)

    print("fun0:", end=" ")
    %timeit fun0(df, w0)
    print("fun1:", end=" ")
    %timeit fun1(df, w)
    print("fun2:", end=" ")
    %timeit fun2(df, w)
    print("fun3:", end=" ")
    %timeit fun3(df, w)
    print("fun4:", end=" ")
    %timeit fun4(df, w)

benchmark(n_rows = 200000, n_cols = 11, n_ws = 3)
benchmark(n_rows = 200000, n_cols = 11, n_ws = 9)
benchmark(n_rows = 200000, n_cols = 31, n_ws = 5)

输出结果(fun0()是使用零填充向量w0的引用):
n_rows:200000, n_cols:11, n_ws:3
fun1: 1.98 ms ± 86.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
fun2: 9.66 ms ± 32.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
fun3: 2.68 ms ± 90.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
fun4: 2.2 ms ± 45.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

n_rows:200000, n_cols:11, n_ws:9
fun1: 1.85 ms ± 28.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
fun2: 11.7 ms ± 54.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
fun3: 3.7 ms ± 84.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
fun4: 3.17 ms ± 29.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

n_rows:200000, n_cols:31, n_ws:5
fun1: 3.08 ms ± 42.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
fun2: 13.1 ms ± 260 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
fun3: 5.48 ms ± 57 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
fun4: 4.98 ms ± 49.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

我已经在MacBook Pro(2015年底)上测试了pandas 1.2.3,numpy 1.20.1和Python 3.9.0。(旧版本的Python也有类似结果)。


Note: 保留 HTML 标签。

1
使用 numpy 中的 dot 函数,带有以下数值:
df[list(w.keys())].values.dot(list(w.values()))
array([2000., 4000., 6000.])

修复了你的错误。
df.mul( pd.Series(w),1).sum(axis=1)
0    2000.0
1    4000.0
2    6000.0
dtype: float64

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