Создавайте серию кортежей из панд DataFrame эффективно - PullRequest
0 голосов
/ 17 октября 2018

Я использую apply(), чтобы создать серию кортежей из значений существующего DataFrame.Мне нужно создать определенный порядок значений в кортеже и заменить NaN во всех столбцах, кроме одного, на '{}'.

Следующие функции работают для получения желаемого результата, но выполнение выполняется довольно медленно:

def build_insert_tuples_series(row):
    # Here I attempt to handle ordering the final tuple
    # I must also replace NaN with "{}" for all but v2 column.
    vals = [row['v2']]
    row_sans_v2 = row.drop(labels=['v2'])
    row_sans_v2.fillna("{}", inplace=True)
    res = [val for val in row_sans_token]
    vals += res
    return tuple(vals)

def generate_insert_values_series(df):
    df['insert_vals'] = df.apply(lambda x: build_insert_tuples_series(x), axis=1)
    return df['insert_vals']

Исходный DataFrame:

    id   v1    v2
0  1.0  foo  quux
1  2.0  bar   foo
2  NaN  NaN   baz

Результирующий DataFrame при вызове generate_insert_values_series(df):

Логика для порядка в последнем кортеже: (v2, ..all_other_columns..)

    id   v1    v2       insert_vals
0  1.0  foo  quux  (quux, 1.0, foo)
1  2.0  bar   foo   (foo, 2.0, bar)
2  NaN  NaN   baz     (baz, {}, {})

Синхронизация функции для генерации результирующего кадра данных:

%%timeit
generate_insert_values_series(df)
100 loops, best of 3: 2.69 ms per loop

Я чувствую, чтоможет быть способ более эффективно построить Серию, но я не уверен, как оптимизировать работу, используя векторизацию или другой подход.

Ответы [ 3 ]

0 голосов
/ 17 октября 2018

Вы не должны хотеть сделать это, так как ваша новая серия утратит все векторизованные функциональные возможности.

Но, если вам необходимо, вы можете избежать apply здесь, используя либоpd.DataFrame.itertuples, составление списка или map.Единственная сложность - переупорядочение столбцов, что можно сделать путем преобразования в list:

df = pd.concat([df]*10000, ignore_index=True)

col_lst = df.columns.tolist()
cols = [col_lst.pop(col_lst.index('v2'))] + col_lst

%timeit list(df[cols].itertuples(index=False))  # 31.3 ms per loop
%timeit [tuple(x) for x in df[cols].values]     # 74 ms per loop
%timeit list(map(tuple, df[cols].values))       # 73 ms per loop

Приведенный выше сравнительный анализ выполняется на Python 3.6.0, но вы, вероятно, найдете их более эффективными, чем apply.на 2.7.Обратите внимание, что преобразование list не является необходимым для окончательной версии, поскольку map возвращает list в v2.7.

Если абсолютно необходимо, вы можете fillna через серию:

s = pd.Series([{} for _ in range(len(df.index))], index=df.index)

for col in df[cols]:
    df[cols].fillna(s)
0 голосов
/ 17 октября 2018

zip, get, mask, fillna и sorted

Один вкладыш для того, что он стоит

df.assign(
    insert_vals=
    [*zip(*map(df.mask(df.isna(), {}).get, sorted(df, key=lambda x: x != 'v2')))])

    id   v1    v2       insert_vals
0  1.0  foo  quux  (quux, 1.0, foo)
1  2.0  bar   foo   (foo, 2.0, bar)
2  NaN  NaN   baz     (baz, {}, {})

Меньше однострочника

get = df.mask(df.isna(), {}).get
key = lambda x: x != 'v2'
cols = sorted(df, key=key)

df.assign(insert_vals=[*zip(*map(get, cols))])

    id   v1    v2       insert_vals
0  1.0  foo  quux  (quux, 1.0, foo)
1  2.0  bar   foo   (foo, 2.0, bar)
2  NaN  NaN   baz     (baz, {}, {})

Это должно работать для устаревшего питона

get = df.mask(df.isna(), {}).get
key = lambda x: x != 'v2'
cols = sorted(df, key=key)

df.assign(insert_vals=zip(*map(get, cols)))
0 голосов
/ 17 октября 2018

Сначала вы можете использовать numpy для замены null значений на dicts

import pandas as pd
import numpy as np

temp = pd.DataFrame({'id':[1,2, None], 'v1':['foo', 'bar', None], 'v2':['quux', 'foo', 'bar']})

def replace_na(col): 
    return np.where(temp[col].isnull(), '{}', temp[col])

def generate_tuple(df):
    df['id'], df['v1'] = replace_na('id'), replace_na('v1')
    return df.apply(lambda x: tuple([x['v2'], x['id'], x['v1']]), axis=1)

Вы получите

%%timeit
temp['insert_tuple'] = generate_tuple(temp)
>>>> 1000 loops, best of 3 : 1ms per loop

Если вы измените generate_tuple return начто-то вроде

def generate_tuple(df):
    df['id'], df['v1'] = replace_na('id'), replace_na('v1')
    return list(zip(df['v2'], df['id'], df['v1']))

ваше усиление становится:

%%timeit
temp['insert_tuple'] = generate_tuple(temp)
1000 loops, best of 3 : 674 µs per loop
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...