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)