根据熊猫中的另一个值更改一个值


107

我试图将我的Stata代码重新编程为Python,以提高速度,而我的方向是PANDAS。但是,我很难集中精力处理数据。

假设我要遍历列标题“ ID”中的所有值。如果该ID与特定数字匹配,那么我想更改两个相应的值FirstName和LastName。

在Stata中,它看起来像这样:

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

因此,这将替换FirstName中与Matt的ID == 103值相对应的所有值。

在PANDAS中,我正在尝试类似的方法

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

不知道从这里去哪里。有任何想法吗?

Answers:


180

一种选择是使用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版)中,这是正确的方法。如果您要担任这个职位,我对您的投票不感兴趣。随便发表您的观点,随时表达您的观点,但我已经对您的观点进行了反思,不同意这一观点。
2013年

11
因特网值得认真研究。无论如何,EMS,我很高兴知道该选项的存在。
Parseltongue

您可能会遇到的一个问题是,csv的列名称中包含句点/点,并且分配混乱。您可以使用以下方式修复列:cols = df.columns cols = cols.map(lambda x:x.replace('。','_')if isinstance(x,str)else x)df.columns = 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 2013年

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=0Series为每一列提供一个对象)。因为我们可以使用列名称访问行中的实际“原始”值,并且可以查看给定行/列中其他单元格的情况,所以这简化了处理。


1
感谢您抽出宝贵的时间写出如此全面的答案。非常感激。
Parseltonguegue '18

感谢您提供的非常有用的答案。一项后续操作-如果我们想通过对列进行数学运算而不是修改字符串来修改列怎么办?例如,使用上面的示例,如果df.animal =='dog'如果我们想将df.age列乘以7,该怎么办?谢谢!
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         

可以通过以下方式更简单地完成同一件事。getdict对象的方法使用'default'参数使得不必将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:哦!非常感谢。
比尔·贝尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.