Как добавить колонку к pd.DataFrame
В Pandas существует по меньшей мере три официальных способа добавить колонку, не включая экзотических:
Способ №1
import pandas as pd df = pd.DataFrame(. ) df['column'] = value
У этого способа самый простой и очевидный синтаксис, поэтому по умолчанию обычно используют именно его. Но наверняка каждый, кто работал с Pandas, получал хотя бы раз в жизни такой неприятный warning при добавлении колонки:
SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead
Этот warning говорит нам, что существует второй способ.
Способ №2
df.loc[:, 'column'] = value
Откуда же берется warning в первом способе? Он возникает, когда выполняется несколько выборок идущих друг за другом, причем на вход следующей выборки подаются результаты предыдущей выборки. В терминологии Pandas это называется chained indexing и выглядит например так:
# Выборка по строкам, потом по колонкам df[df['a'] > 5][['b', 'c']] # Выборка по колонкам, потом по строкам df[['b', 'c']][df['a'] > 5]
Если попытаться модифицировать результаты chained indexing (добавление колонки это тоже модификация), то Pandas не поймет, что мы хотим — добавить колонку в результаты выборки, или добавить колонку в исходный фрейм? Оба примера, приведенные ниже, эквивалентны с точки зрения Pandas:
# Добавить колонку 'b' к исходному фрейму? df[df['a'] > 5]['b'] = 42 # Или к результатам выборки? df1 = df[df['a'] > 5] df1['b'] = 42
Чтобы выдать SettingWithCopyWarning , Pandas запоминает источник данных для каждого фрейма, ‘родительский’ фрейм. Если такой источник существует, т.е. фрейм является подмножеством данных родительского фрейма, то в момент модификации выдается warning.
Второй способ позволяет нам более явным образом сообщить о своих намерениях, т.к. даёт совместить выборку и присваивание в одном выражении.
Более подробно о премудростях chained indexing можно прочитать в документации Pandas или в отличной статье на Medium.
Способ №3
result = df.assign(column=value)
Третий способ не модифицирует исходный фрейм, что в зависимости от ситуации может быть как плюсом (например при повторном выполнении ячейки в Ipython Notebook), так и минусом, загромождая код присваиваниями. Кроме того, при выполнении assign() всегда происходит создание нового фрейма, что теоретически должно быть немного медленнее, чем предыдущие in-place способы.
Наличие нескольких способов сделать одну и ту же простую задачу противоречит известному принципу Zen of Python :
There should be one—and preferably only one—obvious way to do it.
И как оказалось, проблема здесь не только в нарушении философского принципа.
Проблема
Я давно замечал, что при активном добавлении колонок во фреймы код начинает работать подозрительно медленно. Под активным я имею в виду сотни и тысячи добавлений — такие задачи встречаются, когда данные надо разбить на много мелких групп и работать с каждой отдельно. Использование третьего способа, через assign() обычно ускоряло такой код, хотя теоретически он должен работать медленнее двух первых — я списывал это на то, что мне просто показалось, и никогда не делал точных замеров.
Но на последней задаче эта проблема проявилась особенно остро. Скрипт, который должен был пропустить через себя примерно 100Gb данных, и довольно бодро стартовавший с прогнозом времени выполнения 3 часа, был оставлен на ночь. К утру скрипт не выполнил и 20% работы и почти завис, потребляя при этом 100% CPU. В чём же дело?
Запуск скрипта под cProfile выявил занятную картину: основную часть времени процесс находится внутри метода gc.collect() , при том, что я нигде не вызываю сборщик мусора. Такое поведение было бы объяснимым для виртуальной машины Java, работающей в условиях нехватки памяти, тогда бы сборщик мусора активировался на каждый чих. Но Python?
Пришлось поглубже залезть в трассировку вызовов… и следы привели к коду, добавляющему колонки в dataframe! Вот фрагмент кода метода DataFrame._check_setitem_copy() , занимающегося проверкой при добавлении колонки, и выдающего тот самый SettingWithCopyWarning , о котором говорилось выше :
if force or self._is_copy: value = config.get_option('mode.chained_assignment') if value is None: return # see if the copy is not actually referred; if so, then dissolve # the copy weakref try: gc.collect(2) if not gc.get_referents(self._is_copy()): self._is_copy = None return except Exception: pass
В поле self._is_copy хранится weak reference на объект, являющийся ‘родителем’ текущего фрейма. Чтобы проверить, жив ли еще родитель, авторы Pandas не нашли лучшего способа, чем просто запустить сборку мусора во всей виртуальной машине
На тестах, когда в памяти не очень много объектов, сборка мусора отрабатывает практически мгновенно и код не вызывает никаких нареканий. В моём же случае в памяти было закешировано около 10Gb данных, и сборщику мусора приходилось изрядно потрудиться, обходя все эти объекты при каждом добавлении колонки во фрейм.
Решение
Решение было простым — раз блок кода со сборкой мусора исполняется только при наличии ‘родителя’, надо сделать так, чтобы родителя не было. Я просто добавил вызов copy() перед тем местом, где добавляется колонка. После copy() фрейм считается ‘заново рождённым’, и не содержит ссылок на источник данных:
df = df.copy() df['column'] = value
Скрипт сразу заработал намного быстрее, и завершился всего за час
Отмечу, что тормоза были одинаковыми при использовании и первого и второго способа добавления колонки, что неудивительно, т.к. оба они вызывают эту проверку. А что же третий способ, assign() ? Посмотрим на его код, он очень простой (привожу только ветку для Python 3.6):
def assign(self, **kwargs): data = self.copy() for k, v in kwargs.items(): data[k] = com._apply_if_callable(v, data) return data
Как видно, этот код делает ровно то, что я сделал вручную, ускоряя свой скрипт: сначала копирует фрейм, а потом добавляет в него колонки дедовским способом. Именно поэтому использование assign() , вопреки логике, всегда ускоряло работу.
Выводы
Для пользователей Pandas вывод простой: надёжнее всего использовать assign() , и со стороны performance, и со стороны того, что он ограждает пользователя от side effects, связанных с необратимым изменением фрейма. Автор статьи, которую я рекомендовал выше, приходит к тем же выводам. Всегда, когда надо присвоить что-то фрейму, перед присваиванием лучше вызвать df.copy() , чтобы избежать неоднозначностей. И, как показывает мой пример, еще и получить прибавку к скорости!
А разработчикам Pandas хорошо бы или найти способ отказаться от такой brute-force проверки, или хотя бы отразить её наличие в документации.
Вставить столбец в DataFrame с формулой на основе значений из других столбцов
В реальном DataFrame 80 тыс. строк. Необходимо добавить столбец «new», в который вносится формула: max(df.ead — df.recovery, 0) , а также столбец «new1» с формулой scalar * min(df.ead, df.recovery) / df.ead , где scalar=0.03 чтобы получился следующий результат:
ID ead recovery new new1 1 10 15 0 0.03 2 20 15 5 0.0225 3 30 40 0 0.03 4 0 12 0 0 5 nan nan nan nan
Нашел на просторах следующий вариант:
def foo(ead, recovery): return max(ead - recovery, 0) def foo1(ead, recovery): return 0 if ead==0 or ead==np.nan else 0.03 * min(ead,recovery) / ead df['new'] = df.apply(lambda x: foo(x.ead, x.recovery), axis=1) df['new1'] = df.apply(lambda x: foo1(x.ead, x.recovery), axis=1)
Результата достигаю, но время исполнения кода за счет вычисления этого столбца значительно увеличивается. Предполагаю, что сам по себе вариант с .apply() не сильно оптимален. Может быть, есть варианты как-то по другому добавить вычисляемый столбец на основе условия?
Как вставить столбец в фрейм данных Pandas
Часто вам может понадобиться вставить новый столбец в pandas DataFrame. К счастью, это легко сделать с помощью функции вставки () pandas, которая использует следующий синтаксис:
вставка (local, столбец, значение, allow_duplicates = False)
- loc: Индекс для вставки столбца. Первый столбец равен 0.
- столбец: Имя для нового столбца.
- value: Массив значений для нового столбца.
- allow_duplicates: разрешить или запретить совпадение имени нового столбца с именем существующего столбца. По умолчанию — Ложь.
В этом руководстве показано несколько примеров использования этой функции на практике.
Пример 1: вставить новый столбец в качестве первого столбца
Следующий код показывает, как вставить новый столбец в качестве первого столбца существующего DataFrame:
import pandas as pd #create DataFrame df = pd.DataFrame() #view DataFrame df points assists rebounds 0 25 5 11 1 12 7 8 2 15 7 10 3 14 9 6 4 19 12 6 #insert new column 'player' as first column player_vals = ['A', 'B', 'C', 'D', 'E'] df.insert (loc= 0 , column='player', value=player_vals) df player points assists rebounds 0 A 25 5 11 1 B 12 7 8 2 C 15 7 10 3 D 14 9 6 4 E 19 12 6
Пример 2. Вставьте новый столбец в качестве среднего столбца
Следующий код показывает, как вставить новый столбец в качестве третьего столбца существующего DataFrame:
import pandas as pd #create DataFrame df = pd.DataFrame() #insert new column 'player' as third column player_vals = ['A', 'B', 'C', 'D', 'E'] df.insert (loc= 2 , column='player', value=player_vals) df points assists player rebounds 0 25 5 A 11 1 12 7 B 8 2 15 7 C 10 3 14 9 D 6 4 19 12 E 6
Пример 3: вставить новый столбец в качестве последнего столбца
Следующий код показывает, как вставить новый столбец в качестве последнего столбца существующего DataFrame:
import pandas as pd #create DataFrame df = pd.DataFrame() #insert new column 'player' as last column player_vals = ['A', 'B', 'C', 'D', 'E'] df.insert (loc= len(df.columns) , column='player', value=player_vals) df points assists player rebounds 0 25 5 A 11 1 12 7 B 8 2 15 7 C 10 3 14 9 D 6 4 19 12 E 6
Обратите внимание, что использование len(df.columns) позволяет вставить новый столбец в качестве последнего столбца в любом фрейме данных, независимо от того, сколько столбцов в нем может быть.
Вы можете найти полную документацию по функции insert() здесь .
Как добавить столбец в dataframe python
При работе с Dataframe в Pandas одной из распространённой задачей является добавление нового столбца. В этой статье мы рассмотрим несколько способов, как это можно сделать.
Для работы нам понадобится учебный пример:
import pandas as pd
city_data = ‘Город’:[‘Москва’, ‘Казань’, ‘Владивосток’, ‘Санкт-Петербург’, ‘Калининград’],
‘Дата основания’:[‘1147’, ‘1005’, ‘1860’, ‘1703’, ‘1255’],
‘Площадь’:[‘2511’, ‘516’, ‘331’, ‘1439’, ‘223’],
‘Население’:[‘11,9’, ‘1,2’, ‘0,6’, ‘4,9’, ‘0,4’],
‘Погода’:[‘8’, ‘8’, ’17’, ‘9’, ’12’] >
city_df = pd.DataFrame(city_data)
city_df
Теперь у нас есть сформированный Pandas Dataframe, к которому мы будем добавлять новые столбцы разными способами.
Способ 1. Добавление столбца в Pandas при помощи списка
Давайте сформируем новый список, который будет соответствовать содержанию нового столбца:
сity_size = [‘Большой’, ‘Большой’, ‘Средний’, ‘Большой’, ‘Средний’]
Теперь добавим этот список к нашему существующему Dataframe city_df, при этом назовем столбец «Размер города», а также посмотрим получившийся результат:
city_df[‘Размер города’] = сity_size
city_df
Отлично, столбец мы добавили.
Способ 2. Добавление столбца в Pandas при помощи метода insert()
Встроенный в Pandas метод insert() позволяет добавлять столбцы при помощи одной строки кода. Его синтаксис следующий:
insert(loc, column, value, allow_duplicates = False), где
- loc – номер столбца справа, после которого мы хотим добавить новый столбец
- column – название столбца
- value – значения строк в столбце в виде списка
- allow_duplicates – параметр, определяющий можно ли создавать столбец с одинаковым названием.
Давайте применим этот метод, добавив англоязычные названия городов, при этом поставим столбец после названия города на русском языке:
city_df.insert(1, «City», [‘Moscow’, ‘Kazan’, ‘Vladivostok’, ‘St. Petersburg’, ‘Kaliningrad’], False)
city_df
Способ 3. Добавление столбца в Pandas при помощи словаря и метода map()
Этот способ будет полезен в том случае, когда нам нужно создать столбец и вставить в него данные, сопоставляя их с информацией из существующего столбца.
Итак, создаем словарь, в котором первым параметром идет ключ, соответствующий значению существующего столбца, вторым – новый показатель.
city_timezone =
Теперь добавим значения этого словаря в качестве нового столбца в наш существующий Dataframe. В качестве ключевого столбца мы будем использовать «Город»:
city_df[‘Часовой пояс’] = city_df[‘Город’].map(city_timezone)
city_df
Это были основные три способа, с помощью которых можно добавлять столбцы в Pandas. Хотите попрактиковаться сами – скачайте наш ноутбук по этому уроку и вперед.
P.S. Если вы также хотите узнать, как удалять столбцы в Pandas, то прочитайте нашу статью «Как из Pandas удалить столбец?».
