在 pandas 列的切片中搜索值出现次数,获取 indices/mask 次出现次数,然后使用它索引到第二个列切片中

Search slice of pandas columns for value occurrence, get indices/mask of occurrences, then use that to index into a second slice of columns

我有一个大数据框(500K 行 x 100 列)并且 想高效地执行以下搜索和屏蔽操作,但我找不到正确的 pandas/numpy 咒语;如果可以矢量化就更好了:

这是我当前的代码;我尝试了 ilocmeltstack/unstackmasknp.wherenp.select 和其他方法,但无法获得所需的结果:

import numpy as np
from numpy import nan
import pandas as pd

N = 6 # the width of our column-slices of interest

# Sample dataframe
dat = pd.compat.StringIO("""
text,m1,m2,m3,m4,m5,m6,x1,x2,x3,x4,x5,x6\n
'foo',9,3,4,2,1,,      21,22,23,24,25,26\n
'bar',2,3,4,6,5,,      31,32,33,34,35,36\n
'baz',7,3,4,1,,,       11,12,13,14,15,16\n
'qux',2,6,3,4,7,,      41,42,43,44,45,46\n
'gar',3,1,4,7,,,       51,52,53,54,55,56\n
'wal',3,,,,,,          11,12,13,14,15,16\n
'fre',2,3,4,6,5,,      61,62,63,64,65,66\n
'plu',2,3,4,9,1,,      71,72,73,74,75,76\n
'xyz',2,3,4,9,6,1,     81,82,83,84,85,86\n
'thu',1,3,6,4,5,,      51,52,53,54,55,56""".replace(' ',''))

df = pd.read_csv(dat, header=[0])

v = 1 # For example; Actually we want to sweep v from 1:9 ...

# On each row, find the index 'i' of column 'm<i>' which equals v; or NaN if v doesn't occur

df.iloc[:, 1:N+1] == v

(df.iloc[:, 1:N+1] == 1).astype(np.int64)
#    m1  m2  m3  m4  m5  m6
# 0   0   0   0   0   1   0
# 1   0   0   0   0   0   0
# 2   0   0   0   1   0   0
# 3   0   0   0   0   0   0
# 4   0   1   0   0   0   0
# 5   0   0   0   0   0   0
# 6   0   0   0   0   0   0
# 7   0   0   0   0   1   0
# 8   0   0   0   0   0   1
# 9   1   0   0   0   0   0

# np.where() seems useful...
_ = np.where((df.iloc[:, 1:N+1] == 1).astype(np.int64))
# (array([0, 2, 4, 7, 8, 9]), array([4, 3, 1, 4, 5, 0]))

# But you can't directly use df.iloc[ np.where((df.iloc[:, 1:N+1] == 1).astype(np.int64)) ]
# Feels like you want something like df.iloc[ *... ] where we can pass in our intermediate result as separate vectors of row- and col-indices

# can't unpack the np.where output into separate row- and col- indices vectors
irow,icol = *np.where((df.iloc[:, 1:N+1] == 1).astype(np.int64))
SyntaxError: can't use starred expression here

# ...so unpack manually...
irow = _[0]
icol = _[1]
# ... but now can't manage to slice the `x<i>` with those...
df.iloc[irow, 7:13] [:, icol.tolist()] 
TypeError: unhashable type: 'slice'

# Want to get numpy-type indexing, rather than pandas iloc[]
# This also doesn't work:
df.iloc[:, 7:13] [list(zip(*_))]

# Want to slice into the x<i> which are located in df.iloc[:, N+1:2*N+1]

# Or any alternative faster numpy/pandas implementation...

为了可读性,并避免在 df 中使用 float 符号,我首先使用 以下指令将 NaN 值更改为 0 并将其类型更改为 int:

df.fillna(0, downcast='infer', inplace=True)

解决方案 1

现在开始执行主要任务,因为 v == 1。开始于:

x1 = np.argwhere(df.iloc[:, 1:N+1].values == v)

结果是:

[[0 4]
 [2 3]
 [4 1]
 [7 4]
 [8 5]
 [9 0]]

它们是 df.

子集中元素 == v 的索引

然后,"shift"到target元素的索引,在wholedf, 我们必须向每个列索引添加 7(实际上,N+1):

x2 = x1 + [0, N+1]

结果是:

[[ 0 11]
 [ 2 10]
 [ 4  8]
 [ 7 11]
 [ 8 12]
 [ 9  7]]

并得到结果(对于v == 1),执行:

df.values[tuple(x2.T)]

结果是:

array([25, 14, 52, 75, 86, 51], dtype=object)

备选方案:如果您希望在 单个 指令中得到上述结果,运行:

df.values[tuple((np.argwhere(df.iloc[:, 1:N+1].values == v) + [0, N+1]).T)]

上述过程给出了 v == 1 的结果。 这取决于你如何assemble 每次传递的结果(对于v = 1..9)进入 最后结果。你没有在你的问题中描述这个细节(或者我失败了 看到并理解它)。

可能的解决方案之一是:

pd.DataFrame([ df.values[tuple((np.argwhere(df.iloc[:, 1:N+1].values
    == v) + [0, N+1]).T)].tolist() for v in range(1,10) ],
    index=range(1,10)).fillna('-')

给出以下结果:

    0   1   2   3   4   5   6   7   8   9
1  25  14  52  75  86  51   -   -   -   -
2  24  31  41  61  71  81   -   -   -   -
3  22  32  12  43  51  11  62  72  82  52
4  23  33  13  44  53  63  73  83  54   -
5  35  65  55   -   -   -   -   -   -   -
6  34  42  64  85  53   -   -   -   -   -
7  11  45  54   -   -   -   -   -   -   -
8   -   -   -   -   -   -   -   -   -   -
9  21  74  84   -   -   -   -   -   -   -

索引值取自 v 的当前值。 是否对默认感到满意取决于您 个姓名(从0开始的连续数字)。

附加说明:删除第一个值周围的撇号 列(例如,将 'foo' 更改为 foo)。 否则这些撇号是列内容的一部分,我想 你不想要它。 请注意,例如在源列名称的第一行是 without 撇号和 read_csv 足够聪明,可以将它们识别为 string 值。

编辑 - 解决方案 2

另一个可能更简单的解决方案:

因为我们对基础 NumPy table 进行操作,而不是 .values 在许多方面,开始于:

tbl = df.values

然后,对于单个 v 值,使用 [=23= 而不是 argwhere ]:

tbl[:, N+1:][np.nonzero(tbl[:, 1:N+1] == v)]

详情:

  • tbl[:, 1:N+1] - m... 列的切片。
  • np.nonzero(tbl[:, 1:N+1] == v) - 列表的 元组 - 索引 "wanted" 个元素,按轴分组,所以可以直接 用于索引。
  • tbl[:, N+1:] - x<i> 列的切片。

nonzeroargwhere 之间的一个重要区别是 nonzero returns 一个 元组 所以添加一个 "shift" 值到 列号比较难,所以我决定换一个 切片(对于 x<i> 列)。