在pandas列的切片中搜索值的出现,获取出现的索引/掩码,然后使用它索引到第二列的列

时间:2019-05-25 11:28:07

标签: python pandas numpy-slicing

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

  • 在每行上,N列m1,m2,...,m6可以包含与1..9或末尾NaN不同的值。 (存在NaN的理由非常充分,以防止在我们处理此步骤的输出时对不存在的记录进行汇总/取和/均值/等;非常希望保留NaN)
    • 与众不同:保证列m<i>最多包含一个出现的值1..9
  • x1,x2,...,x6与列m<i>关联,并且包含一些整数值
  • 对于范围1..9 中的每个可能值v(我将在分析的顶层从1:9手动扫描v,不用担心这一部分),我想执行以下操作
    • v之一中出现值m<i>的每一行上,找到哪一列m<i>等于v(布尔布尔掩码/数组/索引/任何东西)否则,您更喜欢)
    • v中未发生m<i>的行上,最好我不希望该行有任何结果,甚至不希望NaN
    • 然后我想使用该中间布尔掩码/数组/索引/任何内容从该行的x<i>x1,x2,...,x6)中切出相应的值

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

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...

1 个答案:

答案 0 :(得分:1)

为了便于阅读,并避免在 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 的索引。

然后,要“转移”到整个 df 中的 target 元素的索引, 我们必须在每个列索引中添加 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 的结果。 如何将每次通过的结果(对于 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 )。 否则这些撇号是列内容的一部分,我想 你不要 请注意,例如源列第一行中的名称为不包含 撇号和 read_csv 足够聪明,可以将它们识别为 string 值。

编辑-解决方案2

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

因为我们在基础 NumPy 表上进行操作,而不是 .values 从许多方面出发,以:

tbl = df.values

然后,对于单个 v 值,使用 argwhere 代替 nonzero

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

详细信息:

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

nonzero argwhere 之间的重要区别是 nonzero 返回一个元组,因此在 列号比较困难,所以我决定采用其他方式 切片(用于 {x<i> 列)。