熊猫:将多个类别合并为一个

时间:2015-08-28 03:41:02

标签: python pandas

我们说我有类别,1到10,我想将red分配给值3到5,green分配给1,6和7,以及{{1 }到2,8,9和10。

我该怎么做?如果我试试

blue

我收到错误:df.cat.rename_categories(['red','green','blue']) 但如果我把它放在

ValueError: new categories need to have the same number of items than the old categories!

我会收到错误消息,说明存在重复值。

我能想到的唯一另一种方法是编写一个for循环,它将遍历值的字典并替换它们。是否有更优雅的解决方案?

7 个答案:

答案 0 :(得分:6)

不确定优雅,但是如果你制作一个旧到新类别的词典,就像(注意添加的“紫色”):

>>> m = {"red": [3,4,5], "green": [1,6,7], "blue": [2,8,9,10], "purple": [11]}
>>> m2 = {v: k for k,vv in m.items() for v in vv}
>>> m2
{1: 'green', 2: 'blue', 3: 'red', 4: 'red', 5: 'red', 6: 'green', 
 7: 'green', 8: 'blue', 9: 'blue', 10: 'blue', 11: 'purple'}

您可以使用它来构建新的分类系列:

>>> df.cat.map(m2).astype("category", categories=set(m2.values()))
0    green
1     blue
2      red
3      red
4      red
5    green
6    green
7     blue
8     blue
9     blue
Name: cat, dtype: category
Categories (4, object): [green, purple, red, blue]

如果您确定在列中可以看到所有分类值,则不需要categories=set(m2.values())(如果您关心分类排序,则需要订购的等效值)。但是在这里,如果我们不这样做,我们就不会在生成的分类中看到purple,因为它是根据实际看到的类别构建的。

当然,如果您已经建立了列表['green','blue','red', etc.],那么只需使用它直接创建新的分类列并完全绕过此映射即可。

答案 1 :(得分:3)

似乎pandas.explode (July 18, 2019)发行的pandas-0.25.0恰好适合那里,因此避免了任何循环-

# Mapping dict
In [150]: m = {"red": [3,4,5], "green": [1,6,7], "blue": [2,8,9,10]}

In [151]: pd.Series(m).explode().sort_values()
Out[151]: 
green     1
blue      2
red       3
red       4
red       5
green     6
green     7
blue      8
blue      9
blue     10
dtype: object

因此,结果是一个熊猫系列,其中包含values:index中所有必需的映射。现在,根据用户需求,我们可以直接使用它,也可以根据需要使用dict或series,交换索引和值等不同格式。让我们也来探索它们。

# Mapping obtained
In [152]: s = pd.Series(m).explode().sort_values()

1)输出为dict:

In [153]: dict(zip(s.values, s.index))
Out[153]: 
{1: 'green',
 2: 'blue',
 3: 'red',
 4: 'red',
 5: 'red',
 6: 'green',
 7: 'green',
 8: 'blue',
 9: 'blue',
 10: 'blue'}

2)按系列输出:

In [154]: pd.Series(s.index, s.values)
Out[154]: 
1     green
2      blue
3       red
4       red
5       red
6     green
7     green
8      blue
9      blue
10     blue
dtype: object

答案 2 :(得分:2)

好的,这稍微简单一些,希望能激发更多的对话。

OP的示例输入:

>>> my_data = {'numbers': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}
>>> df = pd.DataFrame(data=my_data)
>>> df.numbers = df.numbers.astype('category')
>>> df.numbers.cat.rename_categories(['green','blue','red', 'red', 'red'
>>>                         'green', 'green', 'blue', 'blue' 'blue'])

这将产生ValueError: Categorical categories must be unique作为OP状态。

我的解决方案:

# write out a dict with the mapping of old to new
>>> remap_cat_dict = {
    1: 'green',
    2: 'blue',
    3: 'red',
    4: 'red',
    5: 'red',
    6: 'green',
    7: 'green',
    8: 'blue',
    9: 'blue',
    10: 'blue' }

>>> df.numbers = df.numbers.map(remap_cat_dict).astype('category')
>>> df.numbers
0    green
1     blue
2      red
3      red
4      red
5    green
6    green
7     blue
8     blue
9     blue
Name: numbers, dtype: category
Categories (3, object): [blue, green, red]

强制您写出完整的字典,其中将旧类别与新类别进行1:1映射,但可读性强。然后,转换非常简单:逐行使用df.apply(在数据系列上使用.apply时隐式)获取每个值,并将其替换为remap_cat_dict中的适当结果。然后将结果转换为类别并覆盖该列。

我几乎遇到了这个确切的问题,我想创建一个新的列,从旧列转换过来的类别较少,这在这里同样容易(并且有益的是不涉及覆盖当前列):

>>> df['colors'] = df.numbers.map(remap_cat_dict).astype('category')
>>> print(df)
  numbers colors
0       1  green
1       2   blue
2       3    red
3       4    red
4       5    red
5       6  green
6       7  green
7       8   blue
8       9   blue
9      10   blue

>>> df.colors

0    green
1     blue
2      red
3      red
4      red
5    green
6    green
7     blue
8     blue
9     blue
Name: colors, dtype: category
Categories (3, object): [blue, green, red]

编辑5/2/20:用df.numbers.apply(lambda x: remap_cat_dict[x])进一步简化df.numbers.map(remap_cat_dict)(感谢@JohnE)

答案 3 :(得分:2)

我当然在这里看不到@DSM的原始答案有问题,但是对于某些人来说,字典理解可能不是最容易阅读的内容(尽管在Python中这是一种相当标准的方法)。

如果您不想使用字典理解,但愿意使用numpy,那么我建议您使用np.select,它与@DSM的答案大致一样简洁,但阅读起来可能更直接,就像@ vector07的答案一样。

import numpy as np 

number = [ df.numbers.isin([3,4,5]), 
           df.numbers.isin([1,6,7]), 
           df.numbers.isin([2,8,9,10]),
           df.numbers.isin([11]) ]

color  = [ "red", "green", "blue", "purple" ]

df.numbers = np.select( number, color )

输出(请注意,这是一个字符串或对象列,但是您当然可以使用astype('category')轻松地转换为类别:

0    green
1     blue
2      red
3      red
4      red
5    green
6    green
7     blue
8     blue
9     blue

基本上是同一件事,但是您也可以使用np.where来做到这一点:

df['numbers2'] = ''
df.numbers2 = np.where( df.numbers.isin([3,4,5]),    "red",    df.numbers2 ) 
df.numbers2 = np.where( df.numbers.isin([1,6,7]),    "green",  df.numbers2 )
df.numbers2 = np.where( df.numbers.isin([2,8,9,10]), "blue",   df.numbers2 )
df.numbers2 = np.where( df.numbers.isin([11]),       "purple", df.numbers2 )

这不会像np.select那样有效,这可能是执行此操作的最有效方法(尽管我没有计时),但是可以说它更具可读性,因为您可以将每个键/值对在同一行上。

答案 4 :(得分:0)

可以这样:

import pandas as pd
df = pd.DataFrame(range(1, 11), columns=['colors'])
color2cod = {"red": [3,4,5], "green": [1,6,7], "blue": [2,8,9,10]}
cod2color = {cod: k for k, cods in color2cod.items() for cod in cods }

df['m'] = df.colors.map(cod2color.get)
df.m = df.m.astype('category')
print('---')
print(df.m.cat.categories)
print('---')
print(df.info())

答案 5 :(得分:0)

我知道这不是问题的确切答案,但我在搜索我的问题时遇到了这个问题,并认为它可能对某人有所帮助。

问题是,在这里您知道要替换为 1 分类的所有值,但我的问题与性别有关,我想要男性、女性和其他但它包含男性、女性和十几个“其他”性别。您如何为所有其他值赋予“其他”的分类类型?

请注意,这不是我的答案,我在这里找到的: Conditionally create an "Other" category in categorical column 答案发布者:user12705352 但我会把它贴在下面。

# Get a list of the top 10 neighborhoods
top10 = df['NEIGHBORHOOD'].value_counts()[:10].index

# At locations where the neighborhood is NOT in the top 10, 
# replace the neighborhood with 'OTHER'
df.loc[~df['NEIGHBORHOOD'].isin(top10), 'NEIGHBORHOOD'] = 'OTHER'

#Create categorical
 df['NEIGHBORHOOD'] = df['NEIGHBORHOOD'].astype(pd.CategoricalDtype(categories=df['NEIGHBORHOOD'].unique(),ordered=False))

答案 6 :(得分:0)

(问这个问题已经有一段时间了。我是数据科学的新手,所以如果我的解决方案不符合要求,请原谅我。)
我认为,更简单的方法是编写一个函数,然后将其映射到系列中。

def color(num):
    blue = [2,8,9,10]
    green = [1,6,7]
    red  = [3,4,5]
    if num in blue:
        return 'blue'
    if num in green:
        return 'green'
    else:
        return 'red'
df.m2 = df.m1.apply(color)