【问题标题】:Efficient way to sum all possible pairs总结所有可能对的有效方法
【发布时间】:2017-11-26 17:22:49
【问题描述】:

我有一个如下所示的数据框:

from random import randint
import pandas as pd

df = pd.DataFrame({"ID": ["a", "b", "c", "d", "e", "f", "g"], 
                   "Size": [randint(0,9) for i in range(0,7)]})

df

  ID  Size
0  a     4
1  b     3
2  c     0
3  d     2
4  e     9
5  f     5
6  g     3

而我想要得到的是这个(也可以是一个矩阵):

sums_df

      a     b    c     d     e     f     g
a   8.0   7.0  4.0   6.0  13.0   9.0   7.0
b   7.0   6.0  3.0   5.0  12.0   8.0   6.0
c   4.0   3.0  0.0   2.0   9.0   5.0   3.0
d   6.0   5.0  2.0   4.0  11.0   7.0   5.0
e  13.0  12.0  9.0  11.0  18.0  14.0  12.0
f   9.0   8.0  5.0   7.0  14.0  10.0   8.0
g   7.0   6.0  3.0   5.0  12.0   8.0   6.0

即,ID 中所有可能对的 Size 值的总和。

现在我有这个简单但低效的代码:

sums_df = pd.DataFrame()

for i in range(len(df)):
    for j in range(len(df)):
        sums_df.loc[i,j] = df.Size[i] + df.Size[j]

sums_df.index = list(df.ID)
sums_df.columns = list(df.ID)

对于像这样的小例子它工作得很好,但对于我的实际数据来说它太长了,我确信可以避免嵌套的 for 循环。你能想出一个更好的方法来做到这一点吗?

感谢您的帮助!

【问题讨论】:

  • 只是好奇:你为什么需要它?
  • 我有另一个数据框,我将其用作 NetworkX 的邻接矩阵(我们称之为 df1),我想将 df1 元素“除以” sums_df 以获得说 df2 。 df1 包含 ID 之间的公共元素的计数,但我也有每个 ID 中的元素数量(这里是 Size,ID 实际上是组的 ID)。这样,我与 df2 一起使用的权重是公共元素的份额,而不是使用 df1 作为邻接矩阵的公共元素的计数。我希望这已经足够清楚了!

标签: python pandas numpy


【解决方案1】:

使用np.add.outer():

In [65]: pd.DataFrame(np.add.outer(df['Size'], df['Size']),
                      columns=df['ID'].values,
                      index=df['ID'].values)
Out[65]:
    a   b  c   d   e   f   g
a   8   7  4   6  13   9   7
b   7   6  3   5  12   8   6
c   4   3  0   2   9   5   3
d   6   5  2   4  11   7   5
e  13  12  9  11  18  14  12
f   9   8  5   7  14  10   8
g   7   6  3   5  12   8   6

更新:节省内存(Pandas 多索引)方法(注意:与前一种方法相比,这种方法要慢得多):

In [33]: r = pd.DataFrame(np.array(list(combinations(df['Size'], 2))).sum(axis=1),
    ...:                  index=pd.MultiIndex.from_tuples(list(combinations(df['ID'], 2))),
    ...:                  columns=['TotalSize']
    ...: )

In [34]: r
Out[34]:
     TotalSize
a b          7
  c          4
  d          6
  e         13
  f          9
  g          7
b c          3
  d          5
  e         12
  f          8
  g          6
c d          2
  e          9
  f          5
  g          3
d e         11
  f          7
  g          5
e f         14
  g         12
f g          8

可以通过如下方式访问:

In [41]: r.loc[('a','b')]
Out[41]:
TotalSize    7
Name: (a, b), dtype: int32

In [42]: r.loc[('a','b'), 'TotalSize']
Out[42]: 7

In [44]: r.loc[[('a','b'), ('c','d')], 'TotalSize']
Out[44]:
a  b    7
c  d    2
Name: TotalSize, dtype: int32

In [43]: r.at[('a','b'), 'TotalSize']
Out[43]: 7

内存使用对比(DF 形状:7000x3):

In [65]: df = pd.concat([df] * 1000, ignore_index=True)

In [66]: df.shape
Out[66]: (7000, 2)

In [67]: r1 = pd.DataFrame(np.add.outer(df['Size'], df['Size']),
    ...:                       columns=df['ID'].values,
    ...:                       index=df['ID'].values)
    ...:

In [68]: r2 = pd.DataFrame(np.array(list(combinations(df['Size'], 2))).sum(axis=1),
    ...:                  index=pd.MultiIndex.from_tuples(list(combinations(df['ID'], 2))),
    ...:                  columns=['TotalSize'])
    ...:

In [69]: r1.memory_usage().sum()/r2.memory_usage().sum()
Out[69]: 2.6685407829018244

速度对比(DF 形状:7000x3):

In [70]: %%timeit
    ...: r1 = pd.DataFrame(np.add.outer(df['Size'], df['Size']),
    ...:                       columns=df['ID'].values,
    ...:                       index=df['ID'].values)
    ...:
180 ms ± 2.99 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [71]: %%timeit
    ...: r2 = pd.DataFrame(np.array(list(combinations(df['Size'], 2))).sum(axis=1),
    ...:                  index=pd.MultiIndex.from_tuples(list(combinations(df['ID'], 2))),
    ...:                  columns=['TotalSize'])
    ...:
17 s ± 325 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

【讨论】:

  • 这正是我所寻找的,而且确实非常快。非常感谢!
  • @atonnerre 粗略地说,它的速度有多快?这就像 10% 的增益或 10 倍的增益?
  • 有什么方法可以利用结果的对称性(或者两者之一:将运行时间减少大约一半,将存储空间减少大约一半)?
  • @Nat 我会同时运行它们并提供它们的执行时间。
  • @Nat @ MaxU 所以我们说的是 0.012 秒而不是大约 16 分钟,而我在 df 中有 1520 行,这真的令人印象深刻!
【解决方案2】:

使用 Numpy 的广播

size = df.Size.values
ids = df.ID.values

pd.DataFrame(
    size[:, None] + size,
    ids, ids
)

    a   b  c   d   e   f   g
a   8   7  4   6  13   9   7
b   7   6  3   5  12   8   6
c   4   3  0   2   9   5   3
d   6   5  2   4  11   7   5
e  13  12  9  11  18  14  12
f   9   8  5   7  14  10   8
g   7   6  3   5  12   8   6

【讨论】:

    【解决方案3】:

    或者类似.values.values.T

    df1=df.set_index('ID')
    df1.values+df1.values.T
    Out[626]: 
    array([[ 8,  7,  4,  6, 13,  9,  7],
           [ 7,  6,  3,  5, 12,  8,  6],
           [ 4,  3,  0,  2,  9,  5,  3],
           [ 6,  5,  2,  4, 11,  7,  5],
           [13, 12,  9, 11, 18, 14, 12],
           [ 9,  8,  5,  7, 14, 10,  8],
           [ 7,  6,  3,  5, 12,  8,  6]], dtype=int64)
    

    更多信息:

    pd.DataFrame(data=df1.values+df1.values.T,index=df.index,columns=df.index)
    Out[627]: 
    ID   a   b  c   d   e   f   g
    ID                           
    a    8   7  4   6  13   9   7
    b    7   6  3   5  12   8   6
    c    4   3  0   2   9   5   3
    d    6   5  2   4  11   7   5
    e   13  12  9  11  18  14  12
    f    9   8  5   7  14  10   8
    g    7   6  3   5  12   8   6
    

    【讨论】:

    • 我在尝试此操作时遇到错误。我不知道为什么,但是当我尝试 "df['Size'].values+df['Size'].values.T" 我得到 "array([10, 0, 12, 8, 16, 0, 16], dtype=int64)".
    • @atonnerre 再试一次,我加了df1=df.set_index('ID'),很抱歉造成混乱。
    • 请注意,我在写的时候提到了设置索引,我自己可以想到这个,而不是你的解决方案,这是非常有价值的! :) 也谢谢你!
    猜你喜欢
    • 2022-01-08
    • 2017-01-20
    • 1970-01-01
    • 1970-01-01
    • 2015-01-28
    • 1970-01-01
    • 2018-03-31
    • 2018-09-18
    • 1970-01-01
    相关资源
    最近更新 更多