[译] Pandas 数据类型概览

article header image

简介

在进行数据分析时,确保使用正确的数据类型很是重要,不然可能会获得意想不到的结果或错误。对 Pandas 而言,它会在不少状况下正确地做出数据类型推断,你能够继续进行分析工做,而无需深刻思考该主题。html

尽管 Pandas 工做得很好,但在数据分析过程当中的某个时刻,你可能须要将数据从一种类型显式转换为另外一种类型。本文将讨论 Pandas 的基本数据类型(即 dtypes),它们如何映射到 python 和 numpy 数据类型,以及从一种 Pandas 类型转换为另外一种类型的几个方式。前端

Pandas 的数据类型

数据类型本质上是编程语言用来理解如何存储和操做数据的内部结构。例如,一个程序须要理解你能够将两个数字加起来,好比 5 + 10 获得 15。或者,若是是两个字符串,好比「cat」和「hat」,你能够将它们链接(加)起来获得「cathat」。python

有关 Pandas 数据类型的一个可能使人困惑的地方是,Pandas、Python 和 numpy 的数据类型之间有一些重叠。下表总结了关键点:android

Pandas dtype 映射:ios

Pandas dtype Python 类型 NumPy 类型 用途
object str string_, unicode_ 文本
int64 int int_, int8, int16, int32, int64, uint8, uint16, uint32, uint64 整数
float64 float float_, float16, float32, float64 浮点数
bool bool bool_ 布尔值
datetime64 NA NA 日期时间
timedelta[ns] NA NA 时间差
category NA NA 有限长度的文本值列表

大多数状况下,你没必要担忧是否应该明确地将熊猫类型强制转换为对应的 NumPy 类型。通常来讲使用 Pandas 的默认 int64float64 就能够。我列出此表的惟一缘由是,有时你可能会在代码行间或本身的分析过程当中看到 Numpy 的类型。git

对于本文,我将重点关注如下 Pandas 类型:github

  • object
  • int64
  • float64
  • datetime64
  • bool

若是你有兴趣,我会再写一篇文章来专门介绍 categorytimedelta 类型。不过本文中概述的基本方法也适用于这些类型。编程

咱们为何关心类型?

数据类型是在你遇到错误或意外结果以前并不会关心的事情之一。不过当你将新数据加载到 Pandas 进行进一步分析时,这也是你应该检查的第一件事情。后端

我将使用一个很是简单的 CSV文件 来讲明在 Pandas 中可能会遇到的一些常见的由数据类型致使的错误。另外,在 github 上也一个示例 notbookbash

import numpy as np
import pandas as pd

df = pd.read_csv("sales_data_types.csv")
复制代码
Customer Number Customer Name 2016 2017 Percent Growth Jan Units Month Day Year Active
0 10002.0 Quest Industries $125,000.00 $162500.00 30.00% 500 1 10 2015 Y
1 552278.0 Smith Plumbing $920,000.00 $101,2000.00 10.00% 700 6 15 2014 Y
2 23477.0 ACME Industrial $50,000.00 $62500.00 25.00% 125 3 29 2016 Y
3 24900.0 Brekke LTD $350,000.00 $490000.00 4.00% 75 10 27 2015 Y
4 651029.0 Harbor Co $15,000.00 $12750.00 -15.00% Closed 2 2 2014 N

乍一看,数据没什么问题,因此咱们能够尝试作一些操做来分析数据。咱们来试一下把 2016 和 2017 年的销售额加起来:

df['2016'] + df['2017']
复制代码
0      $125,000.00$162500.00
1    $920,000.00$101,2000.00
2        $50,000.00$62500.00
3      $350,000.00$490000.00
4        $15,000.00$12750.00
dtype: object
复制代码

这看起来就不对了。咱们但愿将总计加在一块儿,但 Pandas 只是将这两个值链接在一块儿建立了一个长字符串。这个问题的一个线索是 dtype:objectobject 在 Pandas 表明字符串,因此它执行的是字符串操做而不是数学操做。

若是咱们想查看 dataframe 中的全部数据类型,使用 df.dtypes

df.dtypes
复制代码
Customer Number    float64
Customer Name       object
2016                object
2017                object
Percent Growth      object
Jan Units           object
Month                int64
Day                  int64
Year                 int64
Active              object
dtype: object
复制代码

此外,df.info() 函数能够显示更有用的信息。

df.info()
复制代码
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5 entries, 0 to 4
Data columns (total 10 columns):
Customer Number    5 non-null float64
Customer Name      5 non-null object
2016               5 non-null object
2017               5 non-null object
Percent Growth     5 non-null object
Jan Units          5 non-null object
Month              5 non-null int64
Day                5 non-null int64
Year               5 non-null int64
Active             5 non-null object
dtypes: float64(1), int64(3), object(6)
memory usage: 480.0+ bytes
复制代码

查看自动分配的数据类型后,有几个问题:

  • Customer Number 被归为 float64 但它应该是 int64
  • 20162017 这两列被存储为 object,但应该是 float64int64 这样的数值类型
  • Percent GrowthJan Units 也被存储为 object 而不是数值类型
  • MonthDayYear 这三列应该被转换为 datetime64
  • Active 列应该是布尔型

在咱们清洗这些数据类型以前,要对这些数据作更多的附加分析是很是困难的。

为了在 Pandas 中转换数据类型,有三个基本选项:

  • 使用 astype() 来强制转换到合适的 dtype
  • 建立一个自定义函数来转换数据
  • 使用 Pandas 的函数,例如 to_numeric()to_datetime()

使用 astype() 函数

将 Pandas 数据列转换为不一样类型的最简单方法就是用 astype()。例如,要将 Customer Number 转换为整数,咱们能够这样调用:

df['Customer Number'].astype('int')
复制代码
0     10002
1    552278
2     23477
3     24900
4    651029
Name: Customer Number, dtype: int64
复制代码

为了真正修改原始 dataframe 中的客户编号(Customer Number),记得把 astype() 函数的返回值从新赋值给 dataframe,由于 astype() 仅返回数据的副本而不原地修改。

df["Customer Number"] = df['Customer Number'].astype('int')
df.dtypes
复制代码
Customer Number     int64
Customer Name      object
2016               object
2017               object
Percent Growth     object
Jan Units          object
Month               int64
Day                 int64
Year                int64
Active             object
dtype: object
复制代码

如下是客户编号(Customer Number)为整数的新 dataframe:

Customer Number Customer Name 2016 2017 Percent Growth Jan Units Month Day Year Active
0 10002 Quest Industries $125,000.00 $162500.00 30.00% 500 1 10 2015 Y
1 552278 Smith Plumbing $920,000.00 $101,2000.00 10.00% 700 6 15 2014 Y
2 23477 ACME Industrial $50,000.00 $62500.00 25.00% 125 3 29 2016 Y
3 24900 Brekke LTD $350,000.00 $490000.00 4.00% 75 10 27 2015 Y
4 651029 Harbor Co $15,000.00 $12750.00 -15.00% Closed 2 2 2014 N

这一切看起来不错,而且彷佛很简单。让咱们尝试对 2016 列作一样的事情并将其转换为浮点数:

df['2016'].astype('float')
复制代码
ValueError       Traceback (most recent call last)
<ipython-input-45-999869d577b0> in <module>()
----> 1 df['2016'].astype('float')

[一些错误信息]

ValueError: could not convert string to float: '$15,000.00'
复制代码

以相似的方式,咱们能够尝试将 Jan Units 列转换为整数:

df['Jan Units'].astype('int')
复制代码
ValueError         Traceback (most recent call last)

<ipython-input-44-31333711e4a4> in <module>()
----> 1 df['Jan Units'].astype('int')

[一些错误信息]


ValueError: invalid literal for int() with base 10: 'Closed'
复制代码

这两个都返回 ValueError 异常,这意味着转换不起做用。

在这两个例子中,数据都包含不能被解释为数字的值。在销售额(2016)列中,数据包括货币符号以及每一个值中的逗号。在 Jan Units 列中,最后一个值是 "Closed",它不是一个数字;因此咱们获得了异常。

到目前为止,astype() 做为工具看起来并不怎么好。咱们在 Active 列中再试一次。

df['Active'].astype('bool')
复制代码
0    True
1    True
2    True
3    True
4    True
Name: Active, dtype: bool
复制代码

第一眼,这看起来不错,但通过仔细检查,存在一个大问题。全部的值都被解释为 True,但最后一个客户有一个 N 的活动(Active)标志,因此这并不正确。

这一节的重点是 astype() 只有在下列状况下才能工做:

  • 数据是干净的,能够简单地解释为一个数字
  • 你想要将一个数值转换为一个字符串对象

若是数据具备非数字字符或它们间不一样质(homogeneous),那么 astype() 并非类型转换的好选择。你须要进行额外的变换才能完成正确的类型转换。

自定义转换函数

因为该数据转换稍微复杂一些,所以咱们能够构建一个自定义函数,将其应用于每一个值并转换为适当的数据类型。

对于货币转换(这个特定的数据集),下面是一个咱们可使用的简单函数:

def convert_currency(val):
    """ Convert the string number value to a float - Remove $ - Remove commas - Convert to float type """
    new_val = val.replace(',','').replace('$', '')
    return float(new_val)
复制代码

该代码使用python的字符串函数去掉 $,,而后将该值转换为浮点数。在这个特定状况下,咱们能够将值转换为整数,但我选择在这种状况下使用浮点数。

我也怀疑有人会建议咱们对货币使用 Decimal 类型。这不是 Pandas 的本地数据类型,因此我故意坚持使用 float 方式。

另外值得注意的是,该函数将数字转换为 python 的 float,但 Pandas 内部将其转换为 float64。正如前面提到的,我建议你容许 Pandas 在肯定合适的时候将其转换为特定的大小 floatint。你不须要尝试将其转换为更小或更大的字节大小,除非你真的知道为何须要那样作。

如今,咱们可使用 Pandas 的 apply 函数将其应用于 2016 列中的全部值。

df['2016'].apply(convert_currency)
复制代码
0    125000.0
1    920000.0
2     50000.0
3    350000.0
4     15000.0
Name: 2016, dtype: float64
复制代码

成功!全部的值都显示为 float64,咱们能够完成所须要的全部数学计算了。

我确信有经验的读者会问为何我不使用 lambda 函数?在回答以前,先看下咱们能够在一行中使用 lambda 函数完成的操做:

df['2016'].apply(lambda x: x.replace('$', '').replace(',', '')).astype('float')
复制代码

使用 lambda,咱们能够将代码简化为一行,这是很是有效的方法。但我对这种方法有三个主要的意见:

  • 若是你只是在学习 Python / Pandas,或者若是未来会有 Python 新人来维护代码,我认为更长的函数的可读性更好。主要缘由是它能够包含注释,也能够分解为若干步骤。lambda 函数对于新手来讲更难以掌握。
  • 其次,若是你打算在多个列上重复使用这个函数,复制长长的 lambda 函数并不方便。
  • 最后,使用函数能够在使用 read_csv() 时轻松清洗数据。我将在文章结尾处介绍具体的使用方法。

有些人也可能会争辩说,其余基于 lambda 的方法比自定义函数的性能有所提升。但为了教导新手,我认为函数方法更好。

如下是使用 convert_currency 函数转换两个销售(2016 / 2017)列中数据的完整示例。

df['2016'] = df['2016'].apply(convert_currency)
df['2017'] = df['2017'].apply(convert_currency)

df.dtypes
复制代码
Customer Number      int64
Customer Name       object
2016               float64
2017               float64
Percent Growth      object
Jan Units           object
Month                int64
Day                  int64
Year                 int64
Active              object
dtype: object
复制代码

有关使用 lambda 和函数的另外一个例子,咱们能够看看修复 Percent Growth 列的过程。

使用 lambda

df['Percent Growth'].apply(lambda x: x.replace('%', '')).astype('float') / 100
复制代码

用自定义函数作一样的事情:

def convert_percent(val):
    """ Convert the percentage string to an actual floating point percent - Remove % - Divide by 100 to make decimal """
    new_val = val.replace('%', '')
    return float(new_val) / 100

df['Percent Growth'].apply(convert_percent)
复制代码

二者返回的值相同:

0    0.30
1    0.10
2    0.25
3    0.04
4   -0.15
Name: Percent Growth, dtype: float64
复制代码

我将介绍的最后一个自定义函数是使用 np.where() 将活动(Active)列转换为布尔值。有不少方法来解决这个特定的问题。np.where() 方法对于不少类型的问题都颇有用,因此我选择在这里介绍它。

其基本思想是使用 np.where() 函数将全部 Y 值转换为 True,其余全部值为 False

df["Active"] = np.where(df["Active"] == "Y", True, False)
复制代码

其结果以下 dataframe:

Customer Number Customer Name 2016 2017 Percent Growth Jan Units Month Day Year Active
0 10002.0 Quest Industries $125,000.00 $162500.00 30.00% 500 1 10 2015 True
1 552278.0 Smith Plumbing $920,000.00 $101,2000.00 10.00% 700 6 15 2014 True
2 23477.0 ACME Industrial $50,000.00 $62500.00 25.00% 125 3 29 2016 True
3 24900.0 Brekke LTD $350,000.00 $490000.00 4.00% 75 10 27 2015 True
4 651029.0 Harbor Co $15,000.00 $12750.00 -15.00% Closed 2 2 2014 False

dtype 被正确地设置为了 bool

df.dtypes
复制代码
Customer Number    float64
Customer Name       object
2016                object
2017                object
Percent Growth      object
Jan Units           object
Month                int64
Day                  int64
Year                 int64
Active                bool
dtype: object
复制代码

不管你选择使用 lambda 函数,仍是建立一个更标准的 Python 函数,或者是使用其余方法(如 np.where),这些方法都很是灵活,而且能够根据你本身独特的数据需求进行定制。

Pandas 辅助函数

Pandas 在直白的 astype() 函数和复杂的自定义函数之间有一个中间地带。这些辅助函数对于某些数据类型转换很是有用。

若是你顺序读下来,你会注意到我没有对日期列或 Jan Units 列作任何事情。这两种列均可以使用 Pandas 的内置函数(如 pd.to_numeric()pd.to_datetime())进行转换。

Jan Units 转换出现问题的缘由是列中包含一个非数字值。若是咱们尝试使用 astype(),咱们会获得一个错误(如前所述)。pd.to_numeric() 函数能够更优雅地处理这些值:

pd.to_numeric(df['Jan Units'], errors='coerce')
复制代码
0    500.0
1    700.0
2    125.0
3     75.0
4      NaN
Name: Jan Units, dtype: float64
复制代码

这里有几个值得注意的地方。首先,该函数轻松地处理了数据并建立了一个 float64 列。 此外,它会用 NaN 值替换无效的 Closed 值,由于咱们配置了 errors=coerce。咱们能够将 Nan 留在那里,也可使用 fillna(0) 来用 0 填充:

pd.to_numeric(df['Jan Units'], errors='coerce').fillna(0)
复制代码
0    500.0
1    700.0
2    125.0
3     75.0
4      0.0
Name: Jan Units, dtype: float64
复制代码

我最终介绍的转换是将单独的月份、日期和年份列转换为到一个 datetime 类型的列。Pandas 的 pd.to_datetime() 函数 可定制性很好,但默认状况下也十分明智。

pd.to_datetime(df[['Month', 'Day', 'Year']])
复制代码
0   2015-01-10
1   2014-06-15
2   2016-03-29
3   2015-10-27
4   2014-02-02
dtype: datetime64[ns]
复制代码

在这种状况下,函数将这些列组合成适当 datateime64 dtype 的新列。

咱们须要确保将这些值赋值回 dataframe:

df["Start_Date"] = pd.to_datetime(df[['Month', 'Day', 'Year']])
df["Jan Units"] = pd.to_numeric(df['Jan Units'], errors='coerce').fillna(0)
复制代码
Customer Number Customer Name 2016 2017 Percent Growth Jan Units Month Day Year Active Start_Date
0 10002 Quest Industries 125000.0 162500.0 0.30 500.0 1 10 2015 True 2015-01-10
1 552278 Smith Plumbing 920000.0 1012000.0 0.10 700.0 6 15 2014 True 2014-06-15
2 23477 ACME Industrial 50000.0 62500.0 0.25 125.0 3 29 2016 True 2016-03-29
3 24900 Brekke LTD 350000.0 490000.0 0.04 75.0 10 27 2015 True 2015-10-27
4 651029 Harbor Co 15000.0 12750.0 -0.15 NaN 2 2 2014 False 2014-02-02

如今数据已正确转换为咱们须要的全部类型:

df.dtypes
复制代码
Customer Number             int64
Customer Name              object
2016                      float64
2017                      float64
Percent Growth            float64
Jan Units                 float64
Month                       int64
Day                         int64
Year                        int64
Active                       bool
Start_Date         datetime64[ns]
复制代码

Dataframe 已准备好进行分析!

把它们放在一块儿

在数据采集过程当中应该尽早地使用 astype() 和自定义转换函数。若是你有一个打算重复处理的数据文件,而且它老是以相同的格式存储,你能够定义在读取数据时须要应用的 dtypeconverters。将 dtype 视为对数据执行 astype() 颇有帮助。converters 参数容许你将函数应用到各类输入列,相似于上面介绍的方法。

须要注意的是,只能使用 dtypeconverter 函数中的一种来应用于指定的列。若是你尝试将二者应用于同一列,则会跳过 dtype

下面是一个简化的例子,它在数据读入 dataframe 时完成几乎全部的转换:

df_2 = pd.read_csv("sales_data_types.csv",
                   dtype={'Customer Number': 'int'},
                   converters={'2016': convert_currency,
                               '2017': convert_currency,
                               'Percent Growth': convert_percent,
                               'Jan Units': lambda x: pd.to_numeric(x, errors='coerce'),
                               'Active': lambda x: np.where(x == "Y", True, False)
                              })

df_2.dtypes
复制代码
Customer Number      int64
Customer Name       object
2016               float64
2017               float64
Percent Growth     float64
Jan Units          float64
Month                int64
Day                  int64
Year                 int64
Active              object
dtype: object
复制代码

正如前面提到的,我选择了包含用于转换数据的 lambda 示例和函数示例。惟一没法被应用在这里的函数就是那个用来将 MonthDayYear 三列转换到 datetime 列的函数。不过,这还是一个强大的能够帮助改进数据处理流程的约定。

总结

探索新数据集的第一步是确保数据类型设置正确。大部分时间 Pandas 都会作出合理推论,但数据集中有不少细微差异,所以知道如何使用 Pandas 中的各类数据转换选项很是重要。若是你有任何其余建议,或者有兴趣探索 category 数据类型,请随时在下面发表评论。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索