Python Pandas - 将某些列的类型更改为分类

102

我已将以下CSV文件输入iPython Notebook:

public = pd.read_csv("categories.csv")
public

我还导入了pandas作为pd,numpy作为np和matplotlib.pyplot作为plt。以下数据类型存在(以下是摘要 - 大约有100列)

In [36]:   public.dtypes
Out[37]:   parks          object
           playgrounds    object
           sports         object
           roading        object               
           resident       int64
           children       int64

我希望将'parks'、'playgrounds'、'sports'和'roading'更改为类别(它们具有Likert量表响应 - 每列有不同类型的Likert响应,例如一个有"强烈同意"、"同意"等,另一个有"非常重要"、"重要"等),其余保留为int64。

我已经创建了一个单独的数据帧 - public1 - 并使用以下代码将其中一列更改为类别类型:

public1 = {'parks': public.parks}
public1 = public1['parks'].astype('category')

然而,当我试图使用这段代码一次性更改多个数字时,我未能成功:

public1 = {'parks': public.parks,
           'playgrounds': public.parks}
public1 = public1['parks', 'playgrounds'].astype('category')

尽管如此,我不想创建一个只包含类别列的单独数据框。我希望它们在原始数据框中被更改。

我尝试了许多方法来实现这一点,然后尝试了这里的代码:Pandas中更改列类型

public[['parks', 'playgrounds', 'sports', 'roading']] = public[['parks', 'playgrounds', 'sports', 'roading']].astype('category')

并收到以下错误信息:

 NotImplementedError: > 1 ndim Categorical are not supported at this time

有没有一种方法可以将“公园”、“游乐场”、“运动”和“道路”更改为类别(以便随后可以分析Likert比例响应),同时保留“居民”和“孩子”(以及其他94个字符串、整数和浮点数列)不变?

我正在使用Python 2.7。

8个回答

157
有时候,你只需要使用一个 for 循环:

for col in ['parks', 'playgrounds', 'sports', 'roading']:
    public[col] = public[col].astype('category')

3
非常感谢@unutbu,这个方法非常有效。我简直不敢相信它是如此简单,现在感觉自己很愚蠢! - gincard
4
如果我遇到错误怎么办?/Users/air/anaconda/lib/python2.7/site-packages/ipykernel/main.py:7: SettingWithCopyWarning: 在 DataFrame 切片的复制品上尝试设置值。 请改用.loc[row_indexer,col_indexer] = value。请参阅文档中的注意事项:http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy - Jan Sila
5
如果 public 是另一个 DataFrame 的子数据集并且其中的数据是从该其他 DataFrame 中复制过来的,则可能会出现 UserWarning。为了保险起见,Pandas发出 UserWarning 警告您修改 public 不会修改其他DataFrame。如果修改其他DataFrame不是您想要做的或者不是问题,那么可以忽略此警告。如果您希望消除 UserWarning,请在进行形如 public[col] = ... 的赋值之前插入 public.is_copy = False - unutbu
@unutbu 谢谢,我在文档中没有完全理解。这使得它更清晰了。有点像 C++ 中的深拷贝和浅拷贝,对吧?我转换了原始变量然后进行了复制。如果有人也面临这个问题,使用.copy() 在创建子集时也可以起作用。 - Jan Sila
3
@JanSila:是的,没错。public.copy() 也可以用,但需要注意的是如果 public 是一个大型 DataFrame,那么 public.copy() 将比设置标志 public.is_copy = False 的速度要慢得多。(此外,当 public 是复制品时才涉及到 UserWarning,因此我们需要再制作另一个副本来消除警告,似乎有些讽刺。)另一方面,我认为 public.is_copy = False 没有被记录在文档中,我是通过阅读源代码找到的。所以,如果坚持使用文档API是一项优先考虑的任务,你可能希望使用 public = public.copy() - unutbu
@unutbu:注意它已经向量化了(df[sel_cols] = df[sel_cols].astype(new_type)); 还有你的昵称中有一个错别字;) - mirekphd

71
你可以使用 pandas.DataFrame.apply 方法和一个 lambda 表达式来解决这个问题。在你的例子中,你可以使用。
df[['parks', 'playgrounds', 'sports']].apply(lambda x: x.astype('category'))

我不知道有什么方法可以原地执行此操作,所以通常我会得到类似这样的结果:

df[df.select_dtypes(['object']).columns] = df.select_dtypes(['object']).apply(lambda x: x.astype('category'))

如果你不想选择某个特定数据类型的所有列(尽管在你的示例中似乎是想要选择所有 object 类型),显然可以使用具体的列名替换 .select_dtypes


1
非常棒的 +1!真正的 Pythonic 和简洁的解决方案。而且它使用查询来获取所需的列,而不是将它们作为硬编码数组传递,这也很棒。 - DarkCygnus
1
df = df.apply(lambda s: s.astype('category') if s.name in ['parks', 'playgrounds', 'sports'] else s) - LePuppy
df[categoricals] = df[categoricals].apply(lambda x: x.astype('category')) - Milind Dalvi

69

不需要循环,现在Pandas可以直接完成,只需传入要转换的列的列表,Pandas将全部转换。

cols = ['parks', 'playgrounds', 'sports', 'roading']
public[cols] = public[cols].astype('category')

df = pd.DataFrame({'a': ['a', 'b', 'c'], 'b': ['c', 'd', 'e']})

>>     a  b
>>  0  a  c
>>  1  b  d
>>  2  c  e

df.dtypes
>> a    object
>> b    object
>> dtype: object

df[df.columns] = df[df.columns].astype('category')
df.dtypes
>> a    category
>> b    category
>> dtype: object

14

截至pandas 0.19.0版本,What's New中描述了read_csv支持直接解析Categorical列。此答案仅适用于从read_csv开始解析的情况,否则我认为unutbu的答案仍然是最佳选择。如下示例在10,000条记录上进行:

import pandas as pd
import numpy as np

# Generate random data, four category-like columns, two int columns
N=10000
categories = pd.DataFrame({
            'parks' : np.random.choice(['strongly agree','agree', 'disagree'], size=N),
            'playgrounds' : np.random.choice(['strongly agree','agree', 'disagree'], size=N),
            'sports' : np.random.choice(['important', 'very important', 'not important'], size=N),
            'roading' : np.random.choice(['important', 'very important', 'not important'], size=N),
            'resident' : np.random.choice([1, 2, 3], size=N),
            'children' : np.random.choice([0, 1, 2, 3], size=N)
                       })
categories.to_csv('categories_large.csv', index=False)

<0.19.0(或者不指定数据类型的>=19.0)

pd.read_csv('categories_large.csv').dtypes # inspect default dtypes

children        int64
parks          object
playgrounds    object
resident        int64
roading        object
sports         object
dtype: object

>=0.19.0

如果要解析混合的dtypes,可以通过在read_csv中传递一个字典dtype={'colname' : 'category', ...}来实现将其解析为Categorical类型。

pd.read_csv('categories_large.csv', dtype={'parks': 'category',
                                           'playgrounds': 'category',
                                           'sports': 'category',
                                           'roading': 'category'}).dtypes
children          int64
parks          category
playgrounds    category
resident          int64
roading        category
sports         category
dtype: object

性能

如发布说明所述,有轻微加速(本地jupyter笔记本)。

# unutbu's answer
%%timeit
public = pd.read_csv('categories_large.csv')
for col in ['parks', 'playgrounds', 'sports', 'roading']:
    public[col] = public[col].astype('category')
10 loops, best of 3: 20.1 ms per loop

# parsed during read_csv
%%timeit
category_cols = {item: 'category' for item in ['parks', 'playgrounds', 'sports', 'roading']}
public = pd.read_csv('categories_large.csv', dtype=category_cols)
100 loops, best of 3: 14.3 ms per loop

8
为了让事情变得更简单,不需应用、映射或循环。
cols=data.select_dtypes(exclude='int').columns.to_list()
data[cols]=data[cols].astype('category')

这正是我在寻找的。谢谢! - Aref

1
使用列表推导式(避免循环),这将把所有dtype为object的列转换为dtype为category。我将“df”作为数据框名称,以使其更通用。
df[[col for col in df.columns if df[col].dtypes == object]].astype('category', copy=False)

如果您因某种原因想要避免使用“copy=False”参数(正如Python文档告诉我们在使用时要小心),您可以使用以下代码。

df[[col for col in df.columns if df[col].dtypes == object]] = df[[col for col in df.columns if df[col].dtypes == object]].astype('category')

这是我在Stack上的第一个回答,请多多关照。


0

我发现使用 for 循环效果很好。

for col in ['col_variable_name_1', 'col_variable_name_2', ect..]:
    dataframe_name[col] = dataframe_name[col].astype(float)

-1

Jupyter Notebook

在我的情况下,我有一个包含许多对象的大型数据框,我想将其转换为类别。

因此,我选择了对象列,并将任何NA填充为missing,然后将其保存在原始数据框中,如下所示:

# Convert Object Columns to Categories
obj_df =df.select_dtypes(include=['object']).copy()
obj_df=obj_df.fillna('Missing')
for col in obj_df:
    obj_df[col] = obj_df[col].astype('category')
df[obj_df.columns]=obj_df[obj_df.columns]
df.head()

希望这可能成为以后参考的有用资源。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接