Какой самый быстрый способ сопоставить значение из словаря с подмножеством столбца в Pandas? - PullRequest
0 голосов
/ 25 сентября 2018

У меня есть DataFrame с данными анкеты.Баллы по некоторым ответам необходимо перевернуть для дальнейшего анализа.Итак, я хочу сделать следующее:

  1. Выберите строки с вопросами, в которых нужно инвертировать счет;
  2. Сопоставьте счет с новым счетом, используя словарь.

Все соответствующие столбцы в моем DataFrame - dtype 'категория'.Ниже я привел упрощенный пример того, что я пытаюсь выполнить:

import pandas as pd

# create a list of scores and a dictionary to invert the scores:
lst = ['u', 'v', 'w', 'x', 'y']
lst_rev = list(reversed(lst))
dct = dict(zip(lst, lst_rev))

# create the example dataframe:
df = pd.DataFrame({'A':['a', 'b', 'a', 'c', 'a'],
                   'B':lst},
                   dtype='category')

# create a list for selecting the specific rows that need to be remapped:
sel = ['b', 'c']

Первый метод

Первый метод, который я попробовал:

%timeit df['B'] = df.apply(lambda x: dct.get(x['B']) if x['A'] in sel else x['B'], axis=1)

дает следующий результат: 2.6 ms ± 36.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Второй метод

Второй метод был значительно медленнее (в моем наборе данных он показался намного хуже, чем предполагал этот тест):

mask = df['A'].isin(sel)
%timeit df.loc[mask, 'B'] = df.loc[mask, 'B'].map(dct)

выход: 3.56 ms ± 50.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Эти подходы работают, но оба работают довольно плохо на моем наборе данных.У кого-нибудь есть более быстрый метод?Заранее спасибо.

Ответы [ 3 ]

0 голосов
/ 25 сентября 2018

numpy.in1d

b = df.B.values
a = df.A.values
m = np.in1d(a, sel)

b[m] = [*map(dct.get, b[m])]

df

   A  B
0  a  u
1  b  x
2  a  w
3  c  v
4  a  y

Менее инвазивный

b = df.B.values
a = df.A.values
m = np.in1d(a, sel)

np.where(m, [*map(dct.get, b)], b)

map

f = lambda a, b: dct.get(b) if a in sel else b

[*map(lambda t: f(*t), zip(df.A, df.B))]

['u', 'x', 'w', 'v', 'y']
0 голосов
/ 25 сентября 2018

Использование заполненного numpy.ma.masked_array:

a = df.A.values
b = df.B.values
c = list(map(dct.get, b))

out = np.ma.masked_array(b, np.in1d(a, sel)).filled(c)

array(['u', 'x', 'w', 'v', 'y'], dtype=object)

В меньших массивах вещание обеспечивает незначительное ускорение:

a = np.array(df.A)
b = np.array(df.B)
c = list(map(dct.get, b))

mask = (a[:, None] == sel).any(1)

np.ma.masked_array(b, mask).filled(c)
0 голосов
/ 25 сентября 2018

Используйте np.where:

df['B'] = pd.np.where(df['A'].isin(sel), df['B'].map(dct), df['B'])

>>> df
   A  B
0  a  u
1  b  x
2  a  w
3  c  v
4  a  y

Или просто loc, но все сразу, без повторного вызова:

df.loc[df.A.isin(sel), 'B'] = df['B'].map(dct)

>>> df
   A  B
0  a  u
1  b  x
2  a  w
3  c  v
4  a  y

Набольшой кадр данных (10000 записей), np.where заняло около 0,00197 секунд, тогда как ваш apply метод занял около 0,351 секунды:

import timeit

df = pd.DataFrame({'A':np.random.choice(['a','b','c'], 10000),
                   'B':np.random.choice(['u','v','w','x','y'], 10000)})

def method1(df=df, sel=sel):
    return pd.np.where(df['A'].isin(sel), df['B'].map(dct), df['B'])


def method2(df=df,sel=sel):
    return df.apply(lambda x: dct.get(x['B']) if x['A'] in sel else x['B'], axis=1)

>>> timeit.timeit(method1, number=100) / 100
0.001973706789995049
>>> timeit.timeit(method2, number=10) / 10
0.3509046911000041

>>> (method1() == method2()).all()
True
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...