Как мне работать с DataFrame с серией для каждого столбца
Цель и мотивация
Я видел такой вопрос несколько раз и видел много других вопросов, которые включают в себя некоторый элемент этого. Совсем недавно мне пришлось потратить немного времени на объяснение этой концепции в комментариях в поисках подходящих канонических вопросов и ответов. Я не нашел один, и поэтому я думал, что напишу один.
Этот вопрос обычно возникает в отношении конкретной операции, но в равной степени относится к большинству арифметических операций.
- Как вычесть
Series
из каждого столбца вDataFrame
? - Как мне добавить
Series
из каждого столбца вDataFrame
? - Как мне умножить
Series
из каждого столбца вDataFrame
? - Как мне разделить
Series
из каждого столбца вDataFrame
?
Вопрос
Учитывая Series
s
а также DataFrame
df
, Как мне оперировать на каждом столбце df
с s
?
df = pd.DataFrame(
[[1, 2, 3], [4, 5, 6]],
index=[0, 1],
columns=['a', 'b', 'c']
)
s = pd.Series([3, 14], index=[0, 1])
Когда я пытаюсь добавить их, я получаю все np.nan
df + s
a b c 0 1
0 NaN NaN NaN NaN NaN
1 NaN NaN NaN NaN NaN
То, что я думал, я должен получить,
a b c
0 4 5 6
1 18 19 20
3 ответа
Пожалуйста, несите преамбулу. Важно сначала обратиться к некоторым концепциям более высокого уровня. Поскольку моя мотивация - делиться знаниями и учить, я хотел сделать это как можно более ясным.
Полезно создать мысленную модель того, что Series
а также DataFrame
объекты есть.
Анатомия Series
Series
следует рассматривать как расширенный словарь. Это не всегда идеальная аналогия, но мы начнем здесь. Также есть и другие аналогии, которые вы можете сделать, но я нацеливаюсь на словарь, чтобы продемонстрировать цель этого поста.
index
Это ключи, на которые мы можем ссылаться, чтобы получить соответствующие значения. Когда элементы индекса уникальны, сравнение со словарем становится очень близким.
values
Это соответствующие значения, которые указываются индексом.
Анатомия DataFrame
DataFrame
следует рассматривать как словарь Series
или Series
из Series
, В этом случае ключами являются имена столбцов, а значениями являются сами столбцы как Series
объекты. каждый Series
согласен поделиться тем же index
который является индексом DataFrame
,
columns
Это ключи, на которые мы можем ссылаться, чтобы получить на соответствующем Series
,
index
Это индекс, который все Series
ценности согласны делиться
Примечание: RE: columns
а также index
объекты
Это такие же вещи. DataFrame
s index
может быть использован как другой DataFrame
s columns
, На самом деле это происходит, когда вы делаете df.T
чтобы получить транспонирование.
values
Это двумерный массив, который содержит данные в DataFrame
, Реальность такова, что values
НЕ то, что хранится внутри DataFrame
объект. (Ну, иногда это так, но я не собираюсь пытаться описать менеджер блоков). Дело в том, что лучше думать об этом как о доступе к двумерному массиву данных.
Определить пример данных
Это образец pandas.Index
объекты, которые могут быть использованы в качестве index
из Series
или же DataFrame
или может быть использован в качестве columns
из DataFrame
idx_lower = pd.Index([*'abcde'], name='lower')
idx_range = pd.RangeIndex(5, name='range')
Это образец pandas.Series
объекты, которые используют pandas.Index
объекты выше
s0 = pd.Series(range(10, 15), idx_lower)
s1 = pd.Series(range(30, 40, 2), idx_lower)
s2 = pd.Series(range(50, 10, -8), idx_range)
Это образец pandas.DataFrame
объекты, которые используют pandas.Index
объекты выше
df0 = pd.DataFrame(100, index=idx_range, columns=idx_lower)
df1 = pd.DataFrame(
np.arange(np.product(df0.shape)).reshape(df0.shape),
index=idx_range, columns=idx_lower
)
Series
на Series
При работе на двух Series
Выравнивание очевидно. Вы выравниваете index
одного Series
с index
другого.
s1 + s0
lower
a 40
b 43
c 46
d 49
e 52
dtype: int64
Это то же самое, что когда я произвольно перетасовываю одну перед тем, как работать. Индексы все равно будут выравниваться.
s1 + s0.sample(frac=1)
lower
a 40
b 43
c 46
d 49
e 52
dtype: int64
И это НЕ тот случай, когда вместо этого я оперирую со значениями перемешанного Series
, В этом случае у Панд нет index
для выравнивания и, следовательно, работает с позиции.
s1 + s0.sample(frac=1).values
lower
a 42
b 42
c 47
d 50
e 49
dtype: int64
Добавьте скаляр
s1 + 1
lower
a 31
b 33
c 35
d 37
e 39
dtype: int64
DataFrame
на DataFrame
Подобное верно при работе между двумя DataFrame
s
Выравнивание очевидно и делает то, что мы должны сделать
df0 + df1
lower a b c d e
range
0 100 101 102 103 104
1 105 106 107 108 109
2 110 111 112 113 114
3 115 116 117 118 119
4 120 121 122 123 124
Перемешать второй DataFrame
по обеим осям. index
а также columns
все равно выровняется и даст нам то же самое.
df0 + df1.sample(frac=1).sample(frac=1, axis=1)
lower a b c d e
range
0 100 101 102 103 104
1 105 106 107 108 109
2 110 111 112 113 114
3 115 116 117 118 119
4 120 121 122 123 124
То же самое, но добавьте массив, а не DataFrame
, Больше не выравнивается и получит разные результаты.
df0 + df1.sample(frac=1).sample(frac=1, axis=1).values
lower a b c d e
range
0 123 124 121 122 120
1 118 119 116 117 115
2 108 109 106 107 105
3 103 104 101 102 100
4 113 114 111 112 110
Добавьте одномерный массив. Выровняется по столбцам и транслируется по строкам.
df0 + [*range(2, df0.shape[1] + 2)]
lower a b c d e
range
0 102 103 104 105 106
1 102 103 104 105 106
2 102 103 104 105 106
3 102 103 104 105 106
4 102 103 104 105 106
Добавьте скаляр. Нечего согласовывать с таким вещанием на все
df0 + 1
lower a b c d e
range
0 101 101 101 101 101
1 101 101 101 101 101
2 101 101 101 101 101
3 101 101 101 101 101
4 101 101 101 101 101
DataFrame
на Series
Если DataFrame
должны быть словарями Series
а также Series
следует понимать как словари ценностей, тогда естественно, что при работе между DataFrame
а также Series
что они должны быть выровнены их "ключами".
s0:
lower a b c d e
10 11 12 13 14
df0:
lower a b c d e
range
0 100 100 100 100 100
1 100 100 100 100 100
2 100 100 100 100 100
3 100 100 100 100 100
4 100 100 100 100 100
И когда мы работаем, 10
в s0['a']
добавляется ко всему столбцу df0['a']
df0 + s0
lower a b c d e
range
0 110 111 112 113 114
1 110 111 112 113 114
2 110 111 112 113 114
3 110 111 112 113 114
4 110 111 112 113 114
Суть вопроса и смысл поста
А если я захочу s2
а также df0
?
s2: df0:
| lower a b c d e
range | range
0 50 | 0 100 100 100 100 100
1 42 | 1 100 100 100 100 100
2 34 | 2 100 100 100 100 100
3 26 | 3 100 100 100 100 100
4 18 | 4 100 100 100 100 100
Когда я работаю, я получаю все np.nan
как указано в вопросе
df0 + s2
a b c d e 0 1 2 3 4
range
0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
3 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Это не производит то, что мы хотели. Потому что Панды выравнивает index
из s2
с columns
из df0
, columns
результат включает в себя объединение index
из s2
и columns
из df0
,
Мы могли бы подделать это хитрым транспонированием
(df0.T + s2).T
lower a b c d e
range
0 150 150 150 150 150
1 142 142 142 142 142
2 134 134 134 134 134
3 126 126 126 126 126
4 118 118 118 118 118
Но оказывается, что у Панд есть лучшее решение. Существуют методы работы, которые позволяют нам передавать axis
аргумент для указания оси для выравнивания.
-
sub
+
add
*
mul
/
div
**
pow
И поэтому ответ просто
df0.add(s2, axis='index')
lower a b c d e
range
0 150 150 150 150 150
1 142 142 142 142 142
2 134 134 134 134 134
3 126 126 126 126 126
4 118 118 118 118 118
Оказывается axis='index'
является синонимом axis=0
,
Как есть axis='columns'
синоним axis=1
df0.add(s2, axis=0)
lower a b c d e
range
0 150 150 150 150 150
1 142 142 142 142 142
2 134 134 134 134 134
3 126 126 126 126 126
4 118 118 118 118 118
Остальные операции
df0.sub(s2, axis=0)
lower a b c d e
range
0 50 50 50 50 50
1 58 58 58 58 58
2 66 66 66 66 66
3 74 74 74 74 74
4 82 82 82 82 82
df0.mul(s2, axis=0)
lower a b c d e
range
0 5000 5000 5000 5000 5000
1 4200 4200 4200 4200 4200
2 3400 3400 3400 3400 3400
3 2600 2600 2600 2600 2600
4 1800 1800 1800 1800 1800
df0.div(s2, axis=0)
lower a b c d e
range
0 2.000000 2.000000 2.000000 2.000000 2.000000
1 2.380952 2.380952 2.380952 2.380952 2.380952
2 2.941176 2.941176 2.941176 2.941176 2.941176
3 3.846154 3.846154 3.846154 3.846154 3.846154
4 5.555556 5.555556 5.555556 5.555556 5.555556
df0.pow(1 / s2, axis=0)
lower a b c d e
range
0 1.096478 1.096478 1.096478 1.096478 1.096478
1 1.115884 1.115884 1.115884 1.115884 1.115884
2 1.145048 1.145048 1.145048 1.145048 1.145048
3 1.193777 1.193777 1.193777 1.193777 1.193777
4 1.291550 1.291550 1.291550 1.291550 1.291550
Я предпочитаю метод, упомянутый @piSquared (т.е. df.add(s, axis=0)), но другой метод использует apply
вместе с lambda
выполнить действие для каждого столбца в кадре данных:
>>>> df.apply(lambda col: col + s)
a b c
0 4 5 6
1 18 19 20
Чтобы применить лямбда-функцию к строкам, используйте axis=1
:
>>> df.T.apply(lambda row: row + s, axis=1)
0 1
a 4 18
b 5 19
c 6 20
Этот метод может быть полезен, когда преобразование является более сложным, например:
df.apply(lambda col: 0.5 * col ** 2 + 2 * s - 3)
Просто чтобы добавить дополнительный слой из моего собственного опыта. Это расширяет то, что здесь сделали другие. Это показывает, как работать с a, у которого есть дополнительные столбцы, для которых вы хотите сохранить значения. Ниже представлена краткая демонстрация процесса.
import pandas as pd
d = [1.056323, 0.126681,
0.142588, 0.254143,
0.15561, 0.139571,
0.102893, 0.052411]
df = pd.Series(d, index = ['const', '426', '428', '424', '425', '423', '427', '636'])
print(df)
const 1.056323
426 0.126681
428 0.142588
424 0.254143
425 0.155610
423 0.139571
427 0.102893
636 0.052411
d2 = {
'loc': ['D', 'D', 'E', 'E', 'F', 'F', 'G', 'G', 'E', 'D'],
'426': [9, 2, 3, 2, 4, 0, 2, 7, 2, 8],
'428': [2, 4, 1, 0, 2, 1, 3, 0, 7, 8],
'424': [1, 10, 5, 8, 2, 7, 10, 0, 3, 5],
'425': [9, 2, 6, 8, 9, 1, 7, 3, 8, 6],
'423': [4, 2, 8, 7, 9, 6, 10, 5, 9, 9],
'423': [2, 7, 3, 10, 8, 1, 2, 9, 3, 9],
'427': [4, 10, 4, 0, 8, 3, 1, 5, 7, 7],
'636': [10, 5, 6, 4, 0, 5, 1, 1, 4, 8],
'seq': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
}
df2 = pd.DataFrame(d2)
print(df2)
loc 426 428 424 425 423 427 636 seq
0 D 9 2 1 9 2 4 10 1
1 D 2 4 10 2 7 10 5 1
2 E 3 1 5 6 3 4 6 1
3 E 2 0 8 8 10 0 4 1
4 F 4 2 2 9 8 8 0 1
5 F 0 1 7 1 1 3 5 1
6 G 2 3 10 7 2 1 1 1
7 G 7 0 0 3 9 5 1 1
8 E 2 7 3 8 3 7 4 1
9 D 8 8 5 6 9 7 8 1
Чтобы умножить a на a и сохранить разные столбцы
- Создайте список элементов в
DataFrame
а такжеSeries
вы хотите оперировать:
col = ['426', '428', '424', '425', '423', '427', '636']
- Выполните операцию, используя список, и укажите используемую ось:
df2[col] = df2[col].mul(df[col], axis=1)
print(df2)
loc 426 428 424 425 423 427 636 seq
0 D 1.140129 0.285176 0.254143 1.40049 0.279142 0.411572 0.524110 1
1 D 0.253362 0.570352 2.541430 0.31122 0.976997 1.028930 0.262055 1
2 E 0.380043 0.142588 1.270715 0.93366 0.418713 0.411572 0.314466 1
3 E 0.253362 0.000000 2.033144 1.24488 1.395710 0.000000 0.209644 1
4 F 0.506724 0.285176 0.508286 1.40049 1.116568 0.823144 0.000000 1
5 F 0.000000 0.142588 1.779001 0.15561 0.139571 0.308679 0.262055 1
6 G 0.253362 0.427764 2.541430 1.08927 0.279142 0.102893 0.052411 1
7 G 0.886767 0.000000 0.000000 0.46683 1.256139 0.514465 0.052411 1
8 E 0.253362 0.998116 0.762429 1.24488 0.418713 0.720251 0.209644 1
9 D 1.013448 1.140704 1.270715 0.93366 1.256139 0.720251 0.419288 1