Измените одно значение на основе другого значения в пандах

109

Я пытаюсь перепрограммировать свой код Stata на Python для повышения скорости, и меня указали в сторону PANDAS. Однако мне трудно понять, как обрабатывать данные.

Допустим, я хочу перебрать все значения в заголовке столбца «ID». Если этот идентификатор соответствует определенному числу, я хочу изменить два соответствующих значения FirstName и LastName.

В Stata это выглядит так:

replace FirstName = "Matt" if ID==103
replace LastName =  "Jones" if ID==103

Таким образом, это заменяет все значения в FirstName, которые соответствуют значениям ID == 103 на Matt.

В PANDAS я пробую что-то вроде этого

df = read_csv("test.csv")
for i in df['ID']:
    if i ==103:
          ...

Не уверен, куда идти дальше. Любые идеи?

Парселтанг
источник

Ответы:

184

Один из вариантов - использовать функции нарезки и индексации Python для логической оценки мест, в которых выполняется ваше условие, и перезаписать там данные.

Предполагая , что вы можете загрузить свои данные непосредственно в pandasс , pandas.read_csvто следующий код может быть полезным для вас.

import pandas
df = pandas.read_csv("test.csv")
df.loc[df.ID == 103, 'FirstName'] = "Matt"
df.loc[df.ID == 103, 'LastName'] = "Jones"

Как упоминалось в комментариях, вы также можете выполнить назначение для обоих столбцов за один раз:

df.loc[df.ID == 103, ['FirstName', 'LastName']] = 'Matt', 'Jones'

Обратите внимание, что вам потребуется pandasверсия 0.11 или новее, чтобы использовать ее locдля операций присвоения перезаписи.


Другой способ сделать это - использовать так называемое цепное присваивание. Его поведение менее стабильно, и поэтому оно не считается лучшим решением (в документации это явно не рекомендуется ), но полезно знать следующее:

import pandas
df = pandas.read_csv("test.csv")
df['FirstName'][df.ID == 103] = "Matt"
df['LastName'][df.ID == 103] = "Jones"
Эли
источник
16
как насчет того, чтобы добавить еще и этот аромат:df.loc[df.ID == 103, ['FirstName', 'LastName']] = 'Matt', 'Jones'
Boud
2
-1 «Другой способ сделать это - использовать так называемое цепное присваивание». Нет, категорически нет. Это только полезно знать , что прикован назначение не является надежным. Дело не в том, что это надежное, неоптимальное решение, ситуация намного хуже . Вы даже признали это в другом месте на Stack Overflow . Пожалуйста, постарайтесь не создавать иллюзий, что цепное назначение - это жизнеспособный вариант. Первых двух методов, которые вы дали, было достаточно, и они являются предпочтительным способом сделать это.
Филипп Клауд
9
Я не согласен. Я не понимаю , почему вы упорно педантично пытаюсь утверждать , что прикованное назначение не является жизнеспособным способом. Я признал, что это не считается предпочтительным способом. Что вы еще хотите. Нелепо действовать , как это не способ сделать это. Фактически, прямо сейчас в моей системе (версия 0.8) это правильный способ сделать это. Меня не интересуют ваши голоса за, если вы собираетесь занять эту позицию. Не стесняйтесь обозначать свою точку зрения отрицательным голосом, но я уже размышлял над вашей точкой зрения и не согласен с ней.
ely 07
11
Интернет является серьезным бизнесом. В любом случае, EMS, я был признателен, зная, что такая возможность существует.
Parseltongue
Одна из проблем, с которой вы можете столкнуться, заключается в том, что в CSV есть точки / точки в именах столбцов, и назначения перепутаны. Вы можете исправить столбцы, используя что-то вроде этого: cols = df.columns cols = cols.map (lambda x: x.replace ('.', '_') If isinstance (x, str) else x) df.columns = смещ_по_столбцам
ski_squaw
37

Вы можете использовать map, он может отображать значения из молочного склада или даже настраиваемую функцию.

Предположим, это ваш df:

    ID First_Name Last_Name
0  103          a         b
1  104          c         d

Создайте dicts:

fnames = {103: "Matt", 104: "Mr"}
lnames = {103: "Jones", 104: "X"}

И карта:

df['First_Name'] = df['ID'].map(fnames)
df['Last_Name'] = df['ID'].map(lnames)

Результат будет:

    ID First_Name Last_Name
0  103       Matt     Jones
1  104         Mr         X

Или используйте настраиваемую функцию:

names = {103: ("Matt", "Jones"), 104: ("Mr", "X")}
df['First_Name'] = df['ID'].map(lambda x: names[x][0])
Рутгер Кэссис
источник
2
Разве это не вызовет ошибку KeyError, если значения не существуют в вашем dict?
EdChum
1
Пользовательская функция будет, остальные будут работать в любом случае. Но я предположил, что dictон создан для отображения. В противном случае можно выполнить некоторую проверку / очистку на основе чего-то вроде:df.ID.isin(names.keys())
Рутгер Кэссис,
Пользовательская функция может быть расширена до любой (не анонимной) функции.
user989762
14

Исходный вопрос касается конкретного узкого варианта использования. Для тех, кому нужны более общие ответы, вот несколько примеров:

Создание нового столбца с использованием данных из других столбцов

Учитывая приведенный ниже фрейм данных:

import pandas as pd
import numpy as np

df = pd.DataFrame([['dog', 'hound', 5],
                   ['cat', 'ragdoll', 1]],
                  columns=['animal', 'type', 'age'])

In[1]:
Out[1]:
  animal     type  age
----------------------
0    dog    hound    5
1    cat  ragdoll    1

Ниже мы добавляем новый descriptionстолбец как объединение других столбцов, используя +операцию, которая переопределяется для серий. Причудливое форматирование строк, f-строки и т. Д. Здесь не будут работать, поскольку +применяется к скалярам, ​​а не к «примитивным» значениям:

df['description'] = 'A ' + df.age.astype(str) + ' years old ' \
                    + df.type + ' ' + df.animal

In [2]: df
Out[2]:
  animal     type  age                description
-------------------------------------------------
0    dog    hound    5    A 5 years old hound dog
1    cat  ragdoll    1  A 1 years old ragdoll cat

Мы получаем 1 yearsдля кота (вместо 1 year), который мы будем исправлять ниже с помощью условных выражений.

Изменение существующего столбца с помощью условных выражений

Здесь мы заменяем исходный animalстолбец значениями из других столбцов и используем np.whereдля установки условной подстроки на основе значения age:

# append 's' to 'age' if it's greater than 1
df.animal = df.animal + ", " + df.type + ", " + \
    df.age.astype(str) + " year" + np.where(df.age > 1, 's', '')

In [3]: df
Out[3]:
                 animal     type  age
-------------------------------------
0   dog, hound, 5 years    hound    5
1  cat, ragdoll, 1 year  ragdoll    1

Изменение нескольких столбцов с помощью условных выражений

Более гибкий подход - вызвать .apply()весь фрейм данных, а не один столбец:

def transform_row(r):
    r.animal = 'wild ' + r.type
    r.type = r.animal + ' creature'
    r.age = "{} year{}".format(r.age, r.age > 1 and 's' or '')
    return r

df.apply(transform_row, axis=1)

In[4]:
Out[4]:
         animal            type      age
----------------------------------------
0    wild hound    dog creature  5 years
1  wild ragdoll    cat creature   1 year

В приведенном выше коде transform_row(r)функция принимает Seriesобъект, представляющий заданную строку (обозначенное значком axis=1, значение по умолчанию axis=0предоставит Seriesобъект для каждого столбца). Это упрощает обработку, поскольку мы можем получить доступ к фактическим «примитивным» значениям в строке, используя имена столбцов, и иметь видимость других ячеек в данной строке / столбце.

ccpizza
источник
1
Спасибо, что нашли время написать такой исчерпывающий ответ. Очень признателен.
Parseltongue
Спасибо за этот чрезвычайно полезный ответ. Одно продолжение - что, если мы хотим изменить столбец, выполняя математические вычисления в столбце, а не изменяя строку? Например, используя приведенный выше пример, что, если мы хотим умножить столбец df.age на 7, если df.animal == 'dog'? Спасибо!
GbG
1
@GbG: np.whereвероятно, это то, что вы ищете, см., Например, stackoverflow.com/a/42540310/191246, но также возможно, что вы не сможете вписать логику в скалярную операцию, тогда вам нужно будет явно преобразовать ячейка численно похожа на то, как это делается вtransform_row
ccpizza
Спасибо @ccpizza! Именно то, что я искал.
GbG
13

Этот вопрос все еще можно посещать достаточно часто, поэтому стоит предложить дополнение к ответу г-на Кэссиса. dictВстроенный класс может быть суб-классифицироваться таким образом , что по умолчанию возвращается для ключей «пропавших без вести». Этот механизм хорошо работает для панд. Но смотрите ниже.

Таким образом можно избежать ключевых ошибок.

>>> import pandas as pd
>>> data = { 'ID': [ 101, 201, 301, 401 ] }
>>> df = pd.DataFrame(data)
>>> class SurnameMap(dict):
...     def __missing__(self, key):
...         return ''
...     
>>> surnamemap = SurnameMap()
>>> surnamemap[101] = 'Mohanty'
>>> surnamemap[301] = 'Drake'
>>> df['Surname'] = df['ID'].apply(lambda x: surnamemap[x])
>>> df
    ID  Surname
0  101  Mohanty
1  201         
2  301    Drake
3  401         

То же самое можно сделать проще следующим образом. Использование аргумента default для getметода объекта dict делает ненужным создание подкласса dict.

>>> import pandas as pd
>>> data = { 'ID': [ 101, 201, 301, 401 ] }
>>> df = pd.DataFrame(data)
>>> surnamemap = {}
>>> surnamemap[101] = 'Mohanty'
>>> surnamemap[301] = 'Drake'
>>> df['Surname'] = df['ID'].apply(lambda x: surnamemap.get(x, ''))
>>> df
    ID  Surname
0  101  Mohanty
1  201         
2  301    Drake
3  401         
Билл Белл
источник
1
это, безусловно, лучший и самый простой ответ, который я видел, с отличной обработкой по умолчанию. Спасибо.
Brendan
@Brendan: Ой! Огромное спасибо.
Bill Bell