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


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объект для каждого столбца). Это упрощает обработку, поскольку мы можем получить доступ к фактическим «примитивным» значениям в строке, используя имена столбцов, и иметь видимость других ячеек в данной строке / столбце.


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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.