Поиск фрагмента столбцов Pandas по вхождению значения, получение индексов / маски вхождений, а затем использовать его для индексации во второй фрагмент столбцов - PullRequest
0 голосов
/ 25 мая 2019

У меня большой фрейм данных (500K строк x 100 столбцов), и хочу эффективно выполнить следующую операцию поиска и маскирования, но я не могу найти правильное заклинание панд / numpy; еще лучше, если оно может быть векторизовано :

  • в каждой строке, N столбцов m1,m2,...,m6 могут содержать значения, отличные от 1..9, или же конечные NaN. (NaN существуют по очень веской причине, чтобы предотвратить агрегацию / получение суммы / среднего значения и т. Д. Для несуществующих записей, когда мы обрабатываем выходные данные этого шага; очень желательно, чтобы вы сохранили NaN)
    • отличимость: гарантируется, что столбцы m<i> будут содержать не более одного вхождения каждого из значений 1..9
  • столбцы x1,x2,...,x6 связаны со столбцами m<i> и содержат некоторые целочисленные значения
  • Для каждого возможного значения v в диапазоне 1..9 (я вручную увеличу v с 1: 9 на верхнем уровне моего анализа, не беспокойтесь об этой части), Я хочу сделать следующее :
    • в каждой строке, где это значение v встречается в одном из m<i>, найдите, какой столбец m<i> равен v (либо в виде логической маски / массива / индексов / чего-либо еще, что вы предпочитаете)
    • в строках, где v не встречается в m<i>, предпочтительно, я не хочу никакого результата для этой строки, даже NaN
    • тогда я хочу использовать эту промежуточную логическую маску / массив / индексы / что угодно, чтобы вырезать соответствующее значение из x<i> (x1,x2,...,x6) в этой строке

Вот мой текущий код; Я пробовал iloc, melt, stack/unstack, mask, np.where, np.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...

1 Ответ

1 голос
/ 25 мая 2019

Для удобства чтения и во избежание записи float в df я сначала использовал следующую инструкцию, чтобы изменить значения 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]]

Это индексы элементов == v в подмножестве df .

Затем, чтобы "перейти" к индексам target элементов, в целом df , мы должны добавить 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 values.

РЕДАКТИРОВАТЬ - РЕШЕНИЕ 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 возвращает кортеж , поэтому добавление значения "shift"с номером столбца сложнее, поэтому я решил взять другой срез (для x<i> столбцов).

...