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

Я работаю с библиотекой pandas, и я хочу добавить два новых столбца в dataframe 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) возвращает panda Series каждый элемент, состоящий из кортежа z, y. И попытка назначить это на два столбца dataframe создает ValueError.

Ответ 1

Я бы просто использовал 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

Ответ 2

Верхний ответ на мой взгляд ошибочен. Надеемся, никто не импортирует все pandas в свое пространство имен с помощью 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)

60x быстрее, чем zip


Как правило, избегайте применения

Применяется, как правило, не намного быстрее, чем итерация по списку Python. Позвольте проверить производительность for-loop, чтобы сделать то же самое, что и выше

%%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)

Итак, это в два раза медленнее, что не является ужасной регрессией производительности, но если мы cythonize выше, мы получим гораздо лучшую производительность. Предполагая, что вы используете 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)