查看原文
其他

Python 教学 | Pandas 表格字段类型精讲(含类型转换)

分享技巧的 数据Seminar 2023-07-24


Python教学专栏,旨在为初学者提供系统、全面的Python编程学习体验。通过逐步讲解Python基础语言和编程逻辑,结合实操案例,让小白也能轻松搞懂Python!
>>>点击此处查看往期Python教学内容

本文目录

一、前言

二、再谈数据类型

  (一)字段类型错误有何影响

  (二)Pandas中表格初始字段类型从何而来

三、Pandas中关于字段类型的一些“坑

四、字段类型转换

五、总结

六、Python教程

本文共9230个字,阅读大约需要24分钟,欢迎指正!

Part1前言

在使用 Python 处理数据过程中,或者在使用(匹配、入库……)数据之前,我们需要为表格数据的每个字段分配好合适的类型,这样才能高效地进行数据运算并且保证字段含义无误。在 Pandas 系列的第一期文章数据处理必备工具之 Pandas(基础篇)中,我们简单说明了 Pandas 中的表格类型 DataFrame(数据框)中每个字段(列)都有自己的类型,例如整数型、浮点数型、日期类型等。而本期文章,我们将在字段类型这一话题上进行更加深入的探索,同时学习如何对数据字段做类型转换。

本教程基于 pandas 1.5.3 版本书写。本文中所有 Python 代码均在集成开发环境 Visual Studio Code (VScode) 中使用交互式开发环境 Jupyter Notebook 中编写,本文分享的代码请使用 Jupyter Notebook 打开。

💡后台回复关键词“20230616”即可获取本文所有演示代码以及演示用的数据。

Part2再谈数据类型

1 字段类型错误有何影响

在介绍 Pandas 的第一期基础篇文章中,我们已经简单说明了在表格数据 DataFrame 类型中,除了某一个单元格内的数据值,表格的一个字段(即一列)也是有类型属性的,常见的字段类型有 intfloatdateimeobject等。在我们使用的统计数据或面板数据中,一般情况下一列中会存储含义相同的数据值,例如“年份”字段中一般存放四位整数,所以在 Pandas 中字段类型最好是int;“日期”字段中则一般存放日期数据,那么字段类型理应是dateime

那么如果字段类型有误,会带来什么影响呢?首先是数据含义不对版,例如数据中有一字段“年份”,内容都是2022、2023等四位数字,那么这个字段的类型应该是int,但如果不小心搞成了float型(浮点数型),那么其中的数据值就会变成 2022.0、2023.0,如此一来虽然数据值不变,但是加上小数点后数值的含义还是受到了影响。另外,错误的字段类型还会影响数据的运算和匹配等操作,如果数据中“日期”字段如果不是日期类型dateime,而是字符类型string或混合类型object,那么当我们想要根据日期来筛选某一时间段的数据时,是无法根据数据值去做计算的,类似的影响还存在于数据匹配之中,两份数据做匹配,但如果ID字段类型不一致,最后可能也会造成匹配错误。

2Pandas 中表格初始字段类型从何而来

我们在使用 Pandas 做数据处理时,一般还没有特意分配字段类型,数据就已经自动具备了相对合适的类型,那么数据初始的字段类型是怎么来的呢?这个问题要分情况解答,首先,如果表格数据是我们自己使用pd.DataFrame()函数生成的,我们在生成数据时,可以使用参数dtype手动设置字段的类型(仅支持将所有字段设置为同一类型),此时系统尽可能将满足条件的字段设置为指定类型,下面是一个简单的例子。

import pandas as pd    # 导入 pandas
# 创建数据
df = pd.DataFrame([[2008, '北京'],
                    [2012, '伦敦'],
                    [2016, '里约热内卢'],
                    [2020, '东京'],             # 使用 dtype 参数来设置所有字段的类型
                    [2024, '巴黎'],], columns=['年份''奥运承办城市'], dtype='int64')
df.dtypes   # 查看表格 df 中各字段类型
# 下面是返回结果
'''
年份         int64
奥运承办城市    object
'
''

上述代码创建了一个表格,共两个字段:年份奥运承办城市,笔者尝试将所有字段都设置为int64类型,不过数据创建后,发现只有年份字段的类型是指定的类型,这是因为指定类型只会将数据值符合要求的数据值设置为指定类型,例如年份字段中数据值都是整数,就可以被设置为int64类型,而奥运承办城市字段主要是字符数据,不满足int64类型的要求(一个字段中只要有一个数据值不符合要求,即不满足),所以这个字段类型是由 Pandas 系统自动推测分配的。


💡 小知识:

Python 中的整数类型和浮点数类型也有细分类型,例如int型主要包括 int16int32int64等;float型则主要包括 float16float32float64。不同类型类型占用不同的位数和精度,例如int16能够表示的最大整数范围仅为 -32768 到 32767,而int32则可以表示的整数范围是 -2147483648 到 2147483647,int64能够表示的整数值范围更大,达到 19 位整数。与之类似,float16能表示的浮点数仅支持到小数点后 3 位,float32支持小数点后 7 位,float64最大则支持小数点后 15 位,不仅如此,它们存储的浮点数的整数部分也有区分,总的来说,精度越大,能够保存的数值范围也就越广。一般来说(不包括pd.DataFrame()函数,具体可看下文),Pandas 中使用的数值类型大多都是 64 位精度,基本能够满足需求。


我们在设置字段类型时,可以设置位数或精度,也可以直接将类型指定为int,如果使用pd.DataFrame()函数来创建数据表并且设置类型为int,那么这里的 int 将会是 int32如果数据中存在整数超出了int32的最大范围,那么数值将会发生变化,所以需要设置整数类型时,最好设置参数dtype='int64';如果是浮点数的话,函数默认使用的是float64,所以直接设置类型为float即可,不必特意指定精度。

前面说了创建表格并且指定字段类型的情况,如果我们在使用pd.DataFrame()函数创建表格时不指定字段类型会如何呢?答案是 Pandas 会自动根据字段的数据值来推测和设置字段的类型。如果一个字段中的数据值都是整数,Pandas 会自动为其分配整数型(使用pd.DataFrame()的话,默认的是int32),如果一个字段中存在字符型数据或者字段中数据值的类型不唯一,那么就会被分配混合类型object

除了使用 Pandas 创建表格,做数据处理最常用的就是从已有的文件中读取数据,在社科类数据中,常见的有 excel 工作表,csv 文件,Stata表格文件(dta数据)。其中 excel 工作表和 Stata 表格文件这两种表格数据本身就具有类型属性,使用 Pandas 读取时,会自动地继承原有数据的类型并转为 Python 的字段类型,例如 Excel 表中的“数值”类型会自动转为int64float64。如果 Excel 表一列中数据的类型不一致,Pandas 也会继承每个单元格中数据的类型,最后这个字段的类型大概率是混合类型object。在读取 Excel 表时,如果我们不想使用表中原本的类型,而是希望重新设置字段类型,那么在读取时可以通过设置pd.read_excel()函数的dtype参数来控制字段类型,与pd.DataFrame()不同的是,pd.read_excel()函数中的dtype参数支持主动为不同字段分配类型。

df = pd.read_excel('./Excel表.xlsx'
                   dtype={'资产总计(万元)':'float''行业门类名称':'string'})

例如在上述代码中就可以使用dtype来将资产总计(万元)字段的类型设置为float行业门类名称字段的类型设置为string。除了上述用法之外也可以设置参数dtype=str来将表中所有可以设置为字符型的数据值设置为字符型(空值除外),如果是从 csv 文件中读取表格,情况就又不一样了,因为 csv 是纯文本文件,无法保存数据值的类型,所以和读取 Excel 表类似,也可以通过设置参数dtype来设置字段的类型,由于 csv 表格没有数据类型,所以没有被设置类型的字段都将由 Pandas 根据字段中的数据值推测和设置字段的类型,如果在读取时没有设置字段类型,那么所有字段的类型都将由 Pandas 自动推测设置。

Part3Pandas 中关于字段类型的一些“坑”

这一节我们讨论一下字段类型带给 Pandas 的一些“巨坑”。下面我们先熟悉本文演示用的数据(csv 格式数据表),为了方便大家了解使用不同工具对数据本身的影响,下面我们以三种不同的方式打开(或读取)演示数据。

  1. 下图是使用文本模式打开演示用的 csv 数据的截图。由于 csv 数据本就是文本文件,所以用这种方式打开 csv 数据的特色就是“原汁原味”,能够最真实地查看数据,所见即所得,缺点是不易观察表格结构,内容排列比较散乱(首行是表头,一行数据值之间使用逗号分隔开)。

注意观察上图,为了能够清晰的展示后续处理的要点,笔者刻意将数据中存在缺失值或异常值的数据行放在最前面。例如成立日期字段中日期的主要格式为年-月-日,而第 1,3 行数据中日期的格式分别为年/月/日年 月 日;此外,前几行数据中的缺失值和小数的位数也值得留意。

  1. 下图是使用 Excel / WPS 打开演示数据的截图。
仔细观察使用 Excel / WPS 打开数据和使用原生的文本方式的截图,不难发现,Excel / WPS 会对 csv 数据中的数据值进行类型推测。例如成立日期字段中,将格式为年-月-日的文本信息推测并自动转换为日期类型,日期中的分隔符也由短横杠-替换为斜杠/,不过由于第三行中日期1998 12 08类型不符合要求,并没有转为日期,继续保持字符类型(从数据值居左可以看出);除此之外,行业大类代码字段中的数据值也被推测为数字类型,这一点可以看行业大类代码字段第 2 行数据中,原本应该是0100的数据值被转为整数100
  1. 下面我们使用 Python 的 Pandas 库读取上述 csv 数据,读取时不主动设置字段类型,让 Pandas 自动推测数据类型,代码如下。
import pandas as pd   # 导入 pandas 库,按照习惯起别名为 pd
# 读取演示(csv)数据,不指定数据字段类型
data = pd.read_csv('./CPPGD_清洁生产产业(核心)企业微观数据_部分字段样例数据1000条.csv')
# 输出各字段类型
print(data.dtypes)
"""
企业名称         object
成立日期         object
行业大类名称       object
行业大类代码      float64
注册资金(万元)    float64
币种           object
行政区划代码        int64
"
""
data      # 查看读取后的数据

观察上图,正如笔者上文所说,读取 csv 数据时如不通过参数dtype指定字段类型,Pandas 将会根据字段内的数据值推测并分配字段类型。通过上述代码中输出的字段类型和上图可知,由于注册资金(万元)字段中的值都是数字,所以这个字段的类型变成了浮点数类型float64,有一点值得注意,原始数据中(参考上文以文本方式打开数据的截图)该字段中原本存在很多整数,但是 Pandas 读取之后这些整数都变成了六位小数,这是因为字段中同时存在且仅存在整数和浮点数由于浮点类型的精度比整型更高,为了避免数据精度丢失,Pandas 会自动将整型转换为浮点型,准确来说是让字段中所有值都向精度最高的值看齐,比如该字段中精度最高的值为第三行的176092.884369,这是一个六位浮点数,那么字段中其他所有值都会被转换为六位浮点数。这是 Pandas 库中一个让人很无奈的设定。另外可以发现行业大类代码字段的类型也被推测为float64,明明这个字段中不存在浮点数,为什么还会变成被推测为浮点数类型呢?而且其中的数字都变成了一位小数。这是因为该字段中含有缺失值,由于在 Pandas 1.x 版本中只使用了 Numpy 库作为后台,所以读取表格数据时,如果存在缺失值,那么读取后默认使用 Numpy 库中的空值 NaN 代替(在代码中写作 numpy.nan 或 numpy.NaN,意为 Not a number)。这本身无可厚非,但是空值 NaN 是有类型的,它的类型是浮点数float,按照上文的说法,为了避免数据精度丢失,Pandas 自动将字段中精度低的数值转为精度高的数值,这就导致整个字段中的值都变成了浮点数,同时,与使用 Excel 打开 csv 数据相似,第 2 行中数据值0100也被推测为整数100,只不过由于 Pandas 的字段精度特性,再次被转为浮点数100.0

正常情况下,如果 csv 数据一个字段中只存在整数,没有缺失值和浮点数,那么字段类型将会是整数型int64,这一点可以参考上图中行政区划代码字段。在上文中,我们提到正确的字段类型是数据含义的一个保障,例如在演示数据中,行业大类代码行政区划代码两个字段中的数据值不应该是数字类型,而是字符类型,只有保存为字符类型才能准确无误的表示数据的含义,所以遇到类似的情况可以在读取数据时就主动设置这些字段的类型,当然上文的做法是一个真正的“反面教材”。

除了刚才提到的几个字段之外,其他几个字段的类型均是object,上文有提到object是一个混合字段类型,大概意思就是一个字段的类型如果是object,说明该字段中存在不止一种类型的数据。不过当类型是object时,除了字段内含有多种类型的数据,还有一种情况,那就是字段中存在字符类型数据,即使其中的数据值全部都是字符类型,那么在不主动转换或设置的情况下,这个字段的类型也将会是object,而不是string,不过这并不影响正常处理和使用数据。所以读取演示数据后其他字段的类型都是object,因为这些字段中都含有字符串,由此也能看出Pandas 读取 csv 数据时,不会主动将日期格式的文本推测为日期类型,而是保存为字符类型

Part4字段类型转换

在使用 Pandas 对数据进行清洗,筛选,匹配,入库等操作时,可能需要中途改变一些字段的类型,那么这一节我们就来了解一下如何转换数据框DataFrame的字段类型。

在 Pandas 中,astype()函数是最通用,也是最常使用的类型转换函数,既可以一次性对整张表做类型转换,也可以对表中某个字段做类型转换,一般后者的使用场景较多。以上一节读取的演示数据为例,下面是将行政区划代码字段的类型由整数型int64转为字符型string的代码。

print(data['行政区划代码'].dtype)     # 输出转换前该字段的类型
# 转换字段类型为 string
data['行政区划代码'] = data['行政区划代码'].astype('string')  # 或 data['行政区划代码'].astype('string', inplace=True) 
print(data['行政区划代码'].dtype)     # 输出转换后该字段的类型
# 输出值如下
'''
int64
string
'
''

上述第二行代码即为转换类型为string的代码,但不只这一种用法,我们还可以使用下面的代码来将类型转为字符型。

# 方法 2
data['行政区划代码'] = data['行政区划代码'].astype('str')
# 方法 3
data['行政区划代码'] = data['行政区划代码'].astype(str)

不过使用上述两种代码,转换后的字段类型将不是string,而是object。另外,转换数据类型的目的不是改变字段的类型,而是通过转换字段类型的方式改变这个字段内所有值的类型,这一点需要明确


💡 有一点需要特别注意,当需要将一个字段的类型转为字符型时,需要特别注意该字段中是否含有空值 NaNNone 或 pd.NA。因为这些空值虽然不代表任何数据值,但是在类型转换过程中会将NaN转换为字符串'nan',空值None转换为字符串'None',空值pd.NA转换为字符串'<NA>'。如果希望字段转为字符类型的过程中将这些空值都转为空字符串'',那么正确的做法是先填充字段中的缺失值,填充为空字符串'',然后再转换类型,就不会出现问题了。

下面我们尝试将注册资金(万元)字段的类型由浮点数类型float64转为整数型,代码如下。

print(data['注册资金(万元)'].dtype)     # 输出转换前该字段的类型
# 转换字段类型为 int, 在 astype() 中使用 int, 'int', 'int64' 均可
data['注册资金(万元)'] = data['注册资金(万元)'].astype(int)  # 或 data['注册资金(万元)'].astype(int, inplace=True) 
print(data['注册资金(万元)'].dtype)     # 输出转换后该字段的类型
# 输出值如下
'''
float64
int32
'
''

上述代码将含有浮点数的注册资金(万元)字段转换为整数类型,这样做虽然能够达到类型转换的目的,但是字段中所有浮点数的小数部分都会被抹除,等于是直接修改了部分数据的值,所以转换类型时需要谨慎,避免类似情况出现。转换类型后的数据如下图所示。

下面我们再将行业大类名称字段的类型转为整数型,由于这个字段中原本不含小数,所以不会改变字段内的数据值(这个字段本应在读取时就设置为字符型,这里只是为了做演示)。转换类型的代码如下。

# 转换字段类型为 int, 在 astype() 中使用 int, 'int', 'int64' 均可
data['行业大类代码'] = data['行业大类代码'].astype(int)  # 或 data['行业大类代码'].astype(int, inplace=True) 

运行上述代码,结果程序抛出异常:IntCastingNaNError: Cannot convert non-finite values (NA or inf) to integer,这个异常告诉我们 Pandas 中的空值 NaN 不可以被转为整数,实际上正是如此,NaN 的类型是 float,缺失无法被转为整数型,所以转换不会成功,程序自然就会报错。

除了常规的数字型、字符型之间的转换,转换日期也是一个重点,转换为日期类型后,字段中的日期就可以被用来比较运算,进而满足我们需求。下面我们将演示数据中成立日期字段由字符型转换为日期类型,代码如下。

print(data['成立日期'].dtype)     # 输出转换前该字段的类型
# 转换字段类型为日期类型,或者说时间戳类型
data['成立日期'] = data['成立日期'].astype('datetime64[ns]')
print(data['成立日期'].dtype)     # 输出转换后该字段的类型
# 输出值如下
'''
object
datetime64[ns]
'
''

在 Pandas 中,使用astype('datetime64[ns]')就可以将字段中日期类型的字符串转为时间戳,在成立日期字段中,笔者刻意设置了三种不同格式的日期,但是 Pandas 都会智能地将它们转换为日期。

上述转换为日期类型的代码中,使用到的参数值是'datetime64[ns]',这是一种精确到纳秒级别的时间戳类型,除了该类型之外,还存在其他精度的时间戳,例如datatime64[us]表示微秒级别的时间戳,datatime64[h]表示小时级别的时间戳等等。当然,对于社科类的微观数据来说,一般的时间戳只是精确到“日”,我们在转换时使用'datetime64[ns]'就可以了。另外,如果转换前日期字段中存在空值,那么转换过程中这些空值会被转换为 Pandas 中专门用于表示空时间戳的pd.NaT,这样做是为了保持数据类型一致。

最后,我们介绍一下读取上述数据的正确做法,由于行业大类名称行政区划代码字段不应该是数字类型,所以我们在读取时就要设置它们的类型为字符型,代码如下。

## 读取演示(csv)数据,不指定数据字段类型
data = pd.read_csv('./CPPGD_清洁生产产业(核心)企业微观数据_部分字段样例数据1000条.csv',
                   dtype={'行业大类代码':'string''行政区划代码':'string'})  # 将类型设置为 'str' 或 str 也可
print(data.dtypes)
# 输出值如下
'''
企业名称         object
成立日期         object
行业大类名称       object
行业大类代码       string
注册资金(万元)    float64
币种           object
行政区划代码       string
'
''
data   # 输出查看数据

如上图所示,设置类型后,两字段的类型都变为string,由于设置的类型是string而不是str'str',所以字段内的空值也发生了变化(变为 pd.NA,而不是 NaN)。另外,注册资金(万元)字段中存在的整数与浮点数不能共存的问题,我们将在后续的文章中继续深入探讨。

Part5总结

在 Pandas 中,字段类型看似是一个很小的细节,但笔者就曾因为没有深入了解其中的规则规律而吃了不小的亏,相信使用 Python 做数据处理的小伙伴多多少少也都遇到过这类问题。这也说明我们掌握这些知识其实是非常有必要的。本期文章到此结束,下期文章我们将继续分享 Pandas 的其他技巧。

Part6Python教程

 向下活动查看更多



星标⭐我们不迷路!想要文章及时到,文末“在看”少不了!

点击搜索你感兴趣的内容吧

往期推荐


Python 教学 | Pandas 表格数据行列变换

Python 教学 | Pandas 缺失值与重复值的处理方法

Python 教学 | Pandas 妙不可言的条件数据筛选

Python 教学 | Pandas 数据索引与数据选取

Python 教学 | 数据处理必备工具之 Pandas(数据的读取与导出)




数据Seminar




这里是大数据、分析技术与学术研究的三叉路口


文 | 《社科领域大数据治理实务手册》


    欢迎扫描👇二维码添加关注    

点击下方“阅读全文”了解更多

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存