Змініть одне значення на основі іншого значення в пандах


107

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

Скажімо, я хочу повторити всі значення в заголовку стовпця "Ідентифікатор". Якщо цей ідентифікатор відповідає конкретному номеру, я хочу змінити два відповідних значення FirstName та LastName.

У Stata це виглядає приблизно так:

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

Таким чином, це замінює всі значення в FirstName, які відповідають значенням ID == 103 до Метта.

У PANDAS я пробую щось подібне

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

Не впевнений, куди піти звідси. Якісь ідеї?

Відповіді:


181

Одним із варіантів є використання функцій нарізки та індексації 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'
Буд

2
-1 "Ще один спосіб зробити це - використовувати те, що називається прикованим завданням." Ні. Підкреслюється, що ні. Це тільки корисно знати , що прикутий призначення не є надійним. Справа не в тому, що це надійне, неоптимальне рішення, ситуація набагато гірша . Ви навіть визнали це в іншому місці в Stack Overflow . Будь-ласка, намагайтеся уникати ілюзії, що прикута доручення є можливим варіантом. Перших двох методів, які ви дали, було достатньо, і це кращий спосіб зробити це.
Phillip Cloud

9
Я не погоджуюсь. Я не розумію, чому ви наполегливо намагаєтесь стверджувати, що прикуване завдання не є життєздатним способом. Я визнав, що це не вважається кращим способом. Що ще ти хочеш. Безглуздо діяти , як це не спосіб зробити це. Насправді в моїй системі зараз (версія 0.8) це правильний спосіб зробити це. Мене не цікавлять ваші голоси, якщо ви займете цю посаду. Не соромтеся сигналізувати свою точку головою, але я вже подумав про вашу точку і не погоджуюся з цим.
ely

11
Інтернет - це серйозний бізнес. У будь-якому випадку, EMS, я вдячний, знаючи, що існує варіант.
Parseltongue

Одне з проблем, з яким ви можете зіткнутися, - це те, що у csv є періоди / крапки у назвах стовпців, а завдання приєднуються. Ви можете виправити стовпці, використовуючи щось подібне: cols = df.column cols = cols.map (лямбда x: x.replace ('.', '_'), Якщо є речовина (x, str) cols
ski_squaw

37

Ви можете використовувати map, він може зіставити долини з довідника або навіть спеціальної функції.

Припустимо, це ваш df:

    ID First_Name Last_Name
0  103          a         b
1  104          c         d

Створіть дикти:

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, якщо значення не існують у вашому диктаті?
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 == 'собака'? Дякую!
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         

Те ж саме можна зробити простіше наступним чином. Використання аргументу "за замовчуванням" для 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: О! Дуже дякую.
Білл Белл
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.