Панди: створіть два нових стовпчики в кадрі даних зі значеннями, обчисленими з попереднього стовпця


100

Я працюю з бібліотекою панд, і хочу додати два нових стовпчики до фрейму даних dfіз n стовпцями (n> 0).
Ці нові стовпці є результатом застосування функції до одного з стовпців у кадрі даних.

Функція, яка застосовується, така:

def calculate(x):
    ...operate...
    return z, y

Одним із способів створення нового стовпця для функції, що повертає лише значення, є:

df['new_col']) = df['column_A'].map(a_function)

Отже, те, що я хочу, і невдало намагався (*), - це щось на зразок:

(df['new_col_zetas'], df['new_col_ys']) = df['column_A'].map(calculate)

Який найкращий спосіб досягти цього може бути? Я сканував документацію, не маючи підказки.

** df['column_A'].map(calculate)повертає серію панд кожен елемент, що складається з кортежа z, y. І намагаючись призначити це двом стовпцям фрейму даних, створює ValueError. *

Відповіді:


119

Я просто використовую zip:

In [1]: from pandas import *

In [2]: def calculate(x):
   ...:     return x*2, x*3
   ...: 

In [3]: df = DataFrame({'a': [1,2,3], 'b': [2,3,4]})

In [4]: df
Out[4]: 
   a  b
0  1  2
1  2  3
2  3  4

In [5]: df["A1"], df["A2"] = zip(*df["a"].map(calculate))

In [6]: df
Out[6]: 
   a  b  A1  A2
0  1  2   2   3
1  2  3   4   6
2  3  4   6   9

Дякую, чудово, це працює. Нічого подібного я не знайшов у документах для 0.8.1 ... Я вважаю, що я завжди повинен вважати "Серії" списками кортежів ...
joaquin

Чи є якась різниця у виконанні wrt для цього? zip (* map (обчислити, df ["a"])) замість zip (* df ["a"]. map (обчислити)), який також дає (як вище) [(2, 4, 6), ( 3, 6, 9)]?
екта

1
Після отримання нового стовпця я отримую таке попередження: "SettingWithCopyWarning: значення намагається встановити на копії фрагмента з DataFrame. Спробуйте скористатися .loc [row_indexer, col_indexer] = значення замість цього." Чи повинен я турбуватися про це? pandas v.0.15
taras

46

На мою думку, відповідь є хибною. Сподіваємось, ніхто не масово імпортує всі панди у свій простір імен from pandas import *. Також mapметод повинен бути зарезервований для тих часів при передачі йому словника чи серії. Він може приймати функцію, але саме для цього applyвикористовується.

Отже, якщо ви повинні використовувати вищезазначений підхід, я б написав це так

df["A1"], df["A2"] = zip(*df["a"].apply(calculate))

Тут насправді немає причин використовувати zip. Ви можете просто зробити це:

df["A1"], df["A2"] = calculate(df['a'])

Цей другий метод також набагато швидший у великих DataFrames

df = pd.DataFrame({'a': [1,2,3] * 100000, 'b': [2,3,4] * 100000})

DataFrame створений з 300 000 рядків

%timeit df["A1"], df["A2"] = calculate(df['a'])
2.65 ms ± 92.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit df["A1"], df["A2"] = zip(*df["a"].apply(calculate))
159 ms ± 5.24 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

На 60 разів швидше, ніж на блискавці


Загалом, уникайте використання застосунку

Застосувати, як правило, не набагато швидше, ніж повторення над списком Python. Давайте перевіримо працездатність for-циклу, щоб зробити те саме, що описано вище

%%timeit
A1, A2 = [], []
for val in df['a']:
    A1.append(val**2)
    A2.append(val**3)

df['A1'] = A1
df['A2'] = A2

298 ms ± 7.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Отже, це вдвічі повільніше, що не є страшним регресом продуктивності, але якщо ми цитонізуємо вище, ми отримаємо набагато кращі показники. Припустимо, ви використовуєте ipython:

%load_ext cython

%%cython
cpdef power(vals):
    A1, A2 = [], []
    cdef double val
    for val in vals:
        A1.append(val**2)
        A2.append(val**3)

    return A1, A2

%timeit df['A1'], df['A2'] = power(df['a'])
72.7 ms ± 2.16 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Безпосередньо призначення без застосування

Ви можете отримати ще більші покращення швидкості, якщо використовувати прямі векторизовані операції.

%timeit df['A1'], df['A2'] = df['a'] ** 2, df['a'] ** 3
5.13 ms ± 320 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Це використовує переваги надзвичайно швидких векторизованих операцій NumPy замість наших циклів. Тепер ми маємо 30-кратну швидкість над оригіналом.


Найпростіший тест на швидкість з apply

Наведений вище приклад повинен чітко показувати, наскільки applyможе бути повільним , але тільки так його надзвичайно зрозуміло, давайте розглянемо найосновніший приклад. Давайте квадрат на серію з 10 мільйонів чисел із застосуванням і без застосування

s = pd.Series(np.random.rand(10000000))

%timeit s.apply(calc)
3.3 s ± 57.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Без застосування це на 50 разів швидше

%timeit s ** 2
66 ms ± 2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

1
Це дійсно чудова відповідь. Мені хотілося запитати: що ви думаєте про applymapтой випадок, коли вам потрібно реалізувати певну функцію до кожного елемента фрейму даних?
Девід

3
Хоча в цій відповіді є кілька корисних порад, я вважаю, що основна порада використовувати func(series)замість series.apply(func), застосовна лише тоді, коли функція повністю визначена за допомогою операцій, що поводяться аналогічно як для індивідуального значення, так і для серії. Це так у прикладі в першій відповіді, але це не так у запитанні ОП, яке в більш загальному питанні стосується застосування функцій до стовпців. 1/2
Грехем Леа

1
Як приклад, якщо df є: DataFrame({'a': ['Aaron', 'Bert', 'Christopher'], 'b': ['Bold', 'Courageous', 'Distrusted']})і calcє: def calc(x): return x[0], len(x)тоді tdf.a.apply(calc))і calc(tdf.a)повертайте дуже різні речі.
Грехем Леа
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.