数据类型和基本操作
Pandas 是非常著名的开源数据处理库,我们可以通过它完成对数据集进行快速读取、转换、过滤、分析等一系列操作。除此之外,Pandas 拥有强大的缺失数据处理与数据透视功能,可谓是数据预处理中的必备利器。
基础知识点如下:
- 数据类型
- 数据读取
- 数据选择
- 数据删减
- 数据填充
一、引言
Pandas 是非常著名的开源数据处理库,其基于 NumPy 开发,该工具是 Scipy 生态中为了解决数据分析任务而设计。Pandas 纳入了大量库和一些标准的数据模型,提供了高效地操作大型数据集所需的函数和方法。特有的数据结构是 Pandas 的优势和核心。简单来讲,我们可以将任意格式的数据转换为 Pandas 的数据类型,并使用 Pandas 提供的一系列方法进行转换、操作,最终得到我们期望的结果。
二、数据类型
Pandas 的数据类型主要有以下几种,它们分别是:
- Series(一维数组)
- DataFrame(二维数组)
- Panel(三维数组)
- Panel4D(四维数组)
- PanelND(更多维数组)。
其中 Series 和 DataFrame 应用的最为广泛,几乎占据了使用频率 90% 以上。
import numpy as np
import pandas as pd
%matplotlib inline
1. Series
Series 是 Pandas 中最基本的一维数组形式。其可以储存整数、浮点数、字符串(字符串相当于二维数组)等类型的数据。Series 基本结构如下:
pd.Series(data=None, index=None)
F:\Temp1/ipykernel_10284/3044099724.py:1: FutureWarning: The default dtype for empty Series will be 'object' instead of 'float64' in a future version. Specify a dtype explicitly to silence this warning.
pd.Series(data=None, index=None)
Series([], dtype: float64)
其中,data可以是python里面的字典dict,或者是numpy中的narray阵列等。index是数据索引,索引是Pandas数据结构中的一大特性,它主要的功能是帮助我们更快速地定位数据data。
下面,我们基于 Python 字典新建一个示例 Series
dict = {'a':'chengyichen', 'b':'qinlang'}
s = pd.Series(data = dict)
s
a chengyichen
b qinlang
dtype: object
dict = {'a':123, 'b':452}
s = pd.Series(data = dict)
s
a 123
b 452
dtype: int64
可以看出Series里面数字默认类型为 int64,而字符串默认为对象
可以利用type查看Series变量类型
type(s)
pandas.core.series.Series
由于 Pandas 基于 NumPy 开发。那么 NumPy 的数据类型 ndarray 多维数组自然就可以转换为 Pandas 中的数据。而 Series 则可以基于 NumPy 中的一维数据转换。
na = np.ones((3,3), np.uint8)
# 注意:Series作为一维数组,只能接收一维阵列!
s = pd.Series(data = na.flatten())
s
0 1
1 1
2 1
3 1
4 1
5 1
6 1
7 1
8 1
dtype: uint8
可以看出,数据类型和data保持一致且索引值从0开始
2. DataFrame
DataFrame 是 Pandas 中最为常见、最重要且使用频率最高的数据结构。DataFrame 和平常的电子表格或 SQL 表结构相似。你可以把 DataFrame 看成是 Series 的扩展类型,它仿佛是由多个 Series 拼合而成。它和 Series 的直观区别在于,数据不但具有行索引,且具有列索引。
DataFrame 基本结构如下:
pd.DataFrame(data=None, index=None, columns=None)
区别于 Series,其增加了 columns 列索引。DataFrame 可以由以下多个类型的数据构建:
- 一维数组、列表、字典或者 Series 字典。
- 二维或者结构化的 numpy.ndarray。
- 一个 Series 或者另一个 DataFrame。 例如,我们首先使用一个由 Series 组成的字典来构建 DataFrame。
s1 = pd.Series(data={'a':1, 'b':0})
s2 = pd.Series(data={'c':0, 'd':1})
d = pd.DataFrame(data={'one':s1, 'two':s2})
d
| one | two | |
|---|---|---|
| a | 1.0 | NaN |
| b | 0.0 | NaN |
| c | NaN | 0.0 |
| d | NaN | 1.0 |
若是使用字典进行初始化,那么行序列就是Series字典索引,列序列就是输入的字典序列
df = pd.DataFrame({'one': pd.Series([1, 2, 3]),
'two': pd.Series([4, 5, 6])})
df
| one | two | |
|---|---|---|
| 0 | 1 | 4 |
| 1 | 2 | 5 |
| 2 | 3 | 6 |
当Series不指定索引时,DataFrame 的索引同样是从 0 开始。我们也可以直接通过一个列表构成的字典来生成 DataFrame。
df = pd.DataFrame({'one': [1, 2, 3],
'two': [4, 5, 6]})
df
| one | two | |
|---|---|---|
| 0 | 1 | 4 |
| 1 | 2 | 5 |
| 2 | 3 | 6 |
或者反过来,由带字典的列表生成 DataFrame。
df = pd.DataFrame([{'one': 1, 'two': 4},
{'one': 2, 'two': 5},
{'one': 3, 'two': 6}])
df
| one | two | |
|---|---|---|
| 0 | 1 | 4 |
| 1 | 2 | 5 |
| 2 | 3 | 6 |
NumPy 的多维数组非常常用,同样可以基于二维数值来构建一个 DataFrame。
d = pd.DataFrame(np.random.randint(5, size=(2, 4)))
d
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 0 | 2 | 2 | 1 |
| 1 | 2 | 4 | 4 | 2 |
至此,你应该已经清楚了 Pandas 常用的 Series 和 DataFrame 数据类型。Series 实际上可以被初略看出是只有 1 列数据的 DataFrame。当然,这个说法不严谨,二者的核心区别仍然是 Series 没有列索引。你可以观察如下所示由 NumPy 一维随机数组生成的 Series 和 DataFrame。
# size的输入类型要为元组
pd.Series(np.random.randint(5, size=(5,)))
0 1
1 2
2 3
3 0
4 3
dtype: int32
pd.DataFrame(np.random.randint(5, size=(5,)))
| 0 | |
|---|---|
| 0 | 4 |
| 1 | 4 |
| 2 | 1 |
| 3 | 4 |
| 4 | 2 |
关于 Pandas 中的 Panel 等数据类型我们就不再介绍。首先是这些数据类型用的很少,其次就算你用到了,也可以通过从 DataFrame 等学到的技巧进行迁移应用,万变不离其宗。
三、数据读取
我们想要使用 Pandas 来分析数据,那么首先需要读取数据。大多数情况下,数据都来源于外部的数据文件或者数据库。Pandas 提供了一系列的方法来读取外部数据,非常全面。下面,我们以最常用的 CSV 数据文件为例进行介绍。
读取数据 CSV 文件的方法是 pandas.read_csv(),你可以直接传入一个相对路径,或者是网络 URL。
df = pd.read_csv("https://labfile.oss.aliyuncs.com/courses/906/los_census.csv")
df
| Zip Code | Total Population | Median Age | Total Males | Total Females | Total Households | Average Household Size | |
|---|---|---|---|---|---|---|---|
| 0 | 91371 | 1 | 73.5 | 0 | 1 | 1 | 1.00 |
| 1 | 90001 | 57110 | 26.6 | 28468 | 28642 | 12971 | 4.40 |
| 2 | 90002 | 51223 | 25.5 | 24876 | 26347 | 11731 | 4.36 |
| 3 | 90003 | 66266 | 26.3 | 32631 | 33635 | 15642 | 4.22 |
| 4 | 90004 | 62180 | 34.8 | 31302 | 30878 | 22547 | 2.73 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 314 | 93552 | 38158 | 28.4 | 18711 | 19447 | 9690 | 3.93 |
| 315 | 93553 | 2138 | 43.3 | 1121 | 1017 | 816 | 2.62 |
| 316 | 93560 | 18910 | 32.4 | 9491 | 9419 | 6469 | 2.92 |
| 317 | 93563 | 388 | 44.5 | 263 | 125 | 103 | 2.53 |
| 318 | 93591 | 7285 | 30.9 | 3653 | 3632 | 1982 | 3.67 |
319 rows × 7 columns
由于 CSV 存储时是一个二维的表格,那么 Pandas 会自动将其读取为 DataFrame 类型
现在你应该就明白了,DataFrame 是 Pandas 构成的核心。一切的数据,无论是外部读取还是自行生成,我们都需要先将其转换为 Pandas 的 DataFrame 或者 Series 数据类型。实际上,大多数情况下,这一切都是设计好的,无需执行额外的转换工作。
pd.read_ 前缀开始的方法还可以读取各式各样的数据文件,且支持连接数据库。 这里,我们不再依次赘述,你可以阅读 官方文档相应章节pandas.pydata.org/pandas-docs… 熟悉这些方法以及搞清楚这些方法包含的参数。
- 为什么要将读入的数据转化为 Series 或者 DataFrame 结构?
因为 Pandas 针对数据操作的全部方法都是基于 Pandas 支持的数据结构设计的。也就是说,只有 Series 或者 DataFrame 才能使用 Pandas 提供的方法和函数进行处理。所以,学习真正数据处理方法之前,我们需要将数据转换生成为 Series 或 DataFrame 类型。到时候处理完毕导出的时候应该可以转换为其他类型
四、基本操作
通过上面的内容,我们已经知道一个 DataFrame 结构大致由 3 部分组成,它们分别是列名称、索引和数据。
接下来,我们就学习针对 DataFrame 的基本操作。本次课程中,我们不会刻意强调 Series,因为你在 DataFrame 上学习的大多数方法和技巧都适用于对 Series 进行处理,二者同根同源。
上面,我们已经读取了一个外部数据,这是洛杉矶的人口普查数据。有些时候,我们读取的文件很大。如果全部输出预览这些文件,既不美观,又很耗时。还好,Pandas 提供了 head() 和 tail() 方法,它可以帮助我们只预览一小块数据。
df.head() # 默认显示前 5 条
| Zip Code | Total Population | Median Age | Total Males | Total Females | Total Households | Average Household Size | |
|---|---|---|---|---|---|---|---|
| 0 | 91371 | 1 | 73.5 | 0 | 1 | 1 | 1.00 |
| 1 | 90001 | 57110 | 26.6 | 28468 | 28642 | 12971 | 4.40 |
| 2 | 90002 | 51223 | 25.5 | 24876 | 26347 | 11731 | 4.36 |
| 3 | 90003 | 66266 | 26.3 | 32631 | 33635 | 15642 | 4.22 |
| 4 | 90004 | 62180 | 34.8 | 31302 | 30878 | 22547 | 2.73 |
df.tail(7) # 指定显示后 7 条
| Zip Code | Total Population | Median Age | Total Males | Total Females | Total Households | Average Household Size | |
|---|---|---|---|---|---|---|---|
| 312 | 93550 | 74929 | 27.5 | 36414 | 38515 | 20864 | 3.58 |
| 313 | 93551 | 50798 | 37.0 | 25056 | 25742 | 15963 | 3.18 |
| 314 | 93552 | 38158 | 28.4 | 18711 | 19447 | 9690 | 3.93 |
| 315 | 93553 | 2138 | 43.3 | 1121 | 1017 | 816 | 2.62 |
| 316 | 93560 | 18910 | 32.4 | 9491 | 9419 | 6469 | 2.92 |
| 317 | 93563 | 388 | 44.5 | 263 | 125 | 103 | 2.53 |
| 318 | 93591 | 7285 | 30.9 | 3653 | 3632 | 1982 | 3.67 |
Pandas 还提供了统计和描述性方法,方便你从宏观的角度去了解数据集。describe() 相当于对数据集进行概览,会输出该数据集每一列数据的计数、最大值、最小值等。
df.describe()
| Zip Code | Total Population | Median Age | Total Males | Total Females | Total Households | Average Household Size | |
|---|---|---|---|---|---|---|---|
| count | 319.000000 | 319.000000 | 319.000000 | 319.000000 | 319.000000 | 319.000000 | 319.000000 |
| mean | 91000.673981 | 33241.341693 | 36.527586 | 16391.564263 | 16849.777429 | 10964.570533 | 2.828119 |
| std | 908.360203 | 21644.417455 | 8.692999 | 10747.495566 | 10934.986468 | 6270.646400 | 0.835658 |
| min | 90001.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 25% | 90243.500000 | 19318.500000 | 32.400000 | 9763.500000 | 9633.500000 | 6765.500000 | 2.435000 |
| 50% | 90807.000000 | 31481.000000 | 37.100000 | 15283.000000 | 16202.000000 | 10968.000000 | 2.830000 |
| 75% | 91417.000000 | 44978.000000 | 41.000000 | 22219.500000 | 22690.500000 | 14889.500000 | 3.320000 |
| max | 93591.000000 | 105549.000000 | 74.000000 | 52794.000000 | 53185.000000 | 31087.000000 | 4.670000 |
Pandas 基于 NumPy 开发,所以任何时候你都可以通过 .values 将 DataFrame 转换为 NumPy 数组。
# 但是丢失了列索引
df.values
array([[9.1371e+04, 1.0000e+00, 7.3500e+01, ..., 1.0000e+00, 1.0000e+00, 1.0000e+00],
[9.0001e+04, 5.7110e+04, 2.6600e+01, ..., 2.8642e+04, 1.2971e+04, 4.4000e+00],
[9.0002e+04, 5.1223e+04, 2.5500e+01, ..., 2.6347e+04, 1.1731e+04, 4.3600e+00],
...,
[9.3560e+04, 1.8910e+04, 3.2400e+01, ..., 9.4190e+03, 6.4690e+03, 2.9200e+00],
[9.3563e+04, 3.8800e+02, 4.4500e+01, ..., 1.2500e+02, 1.0300e+02, 2.5300e+00],
[9.3591e+04, 7.2850e+03, 3.0900e+01, ..., 3.6320e+03, 1.9820e+03, 3.6700e+00]])
这也就说明了,你可以同时使用 Pandas 和 NumPy 提供的 API 对同一数据进行操作,并在二者之间进行随意转换。这就是一个非常灵活的工具生态圈。
除了 .values,DataFrame 支持的常见属性可以通过 官方文档相应章节 pandas.pydata.org/pandas-docs… 查看。其中常用的有:
df.index # 查看索引
RangeIndex(start=0, stop=319, step=1)
df.columns # 查看列名
Index(['Zip Code', 'Total Population', 'Median Age', 'Total Males', 'Total Females', 'Total Households', 'Average Household Size'],
dtype='object')
df.shape # 查看形状
(319, 7)
五、数据选择
在数据预处理过程中,我们往往会对数据集进行切分,只将需要的某些行、列,或者数据块保留下来,输出到下一个流程中去。这也就是所谓的数据选择,或者数据索引。
由于 Pandas 的数据结构中存在索引、标签,所以我们可以通过多轴索引完成对数据的选择。
1. 基于索引数字选择
当我们新建一个 DataFrame 之后,如果未自己指定行索引或者列对应的标签,那么 Pandas 会默认从 0 开始以数字的形式作为行索引,并以数据集的第一行作为列对应的标签。其实,这里的「列」也有数字索引,默认也是从 0 开始,只是未显示出来。
所以,我们首先可以基于数字索引对数据集进行选择。这里用到的 Pandas 中的 .iloc 方法。该方法可以接受的类型有:
- 整数。例如:5
- 整数构成的列表或数组。例如:[1, 2, 3]
- 布尔数组。
- 可返回索引值的函数或参数。
首先,我们可以选择前 3 行数据。这和 Python 或者 NumPy 里面的切片很相似。
df.iloc[:3]
| Zip Code | Total Population | Median Age | Total Males | Total Females | Total Households | Average Household Size | |
|---|---|---|---|---|---|---|---|
| 0 | 91371 | 1 | 73.5 | 0 | 1 | 1 | 1.00 |
| 1 | 90001 | 57110 | 26.6 | 28468 | 28642 | 12971 | 4.40 |
| 2 | 90002 | 51223 | 25.5 | 24876 | 26347 | 11731 | 4.36 |
我们还可以选择特定的一行。
df.iloc[5]
Zip Code 90005.0
Total Population 37681.0
Median Age 33.9
Total Males 19299.0
Total Females 18382.0
Total Households 15044.0
Average Household Size 2.5
Name: 5, dtype: float64
那么选择多行,是不是 df.iloc[1, 3, 5] 这样呢?
答案是错误的。df.iloc[] 的 [[行],[列]] 里面可以同时接受行和列的位置,如果你直接键入 df.iloc[1, 3, 5] 就会报错
## 如果你想要选择 2,4,6 行,从0开始,可以这样做。
df.iloc[[1, 3, 5]]
| Zip Code | Total Population | Median Age | Total Males | Total Females | Total Households | Average Household Size | |
|---|---|---|---|---|---|---|---|
| 1 | 90001 | 57110 | 26.6 | 28468 | 28642 | 12971 | 4.40 |
| 3 | 90003 | 66266 | 26.3 | 32631 | 33635 | 15642 | 4.22 |
| 5 | 90005 | 37681 | 33.9 | 19299 | 18382 | 15044 | 2.50 |
选择行学会以后,选择列就应该能想到怎么办了。例如,我们要选择第 2-4 列。
df.iloc[:, 1:4]
| Total Population | Median Age | Total Males | |
|---|---|---|---|
| 0 | 1 | 73.5 | 0 |
| 1 | 57110 | 26.6 | 28468 |
| 2 | 51223 | 25.5 | 24876 |
| 3 | 66266 | 26.3 | 32631 |
| 4 | 62180 | 34.8 | 31302 |
| ... | ... | ... | ... |
| 314 | 38158 | 28.4 | 18711 |
| 315 | 2138 | 43.3 | 1121 |
| 316 | 18910 | 32.4 | 9491 |
| 317 | 388 | 44.5 | 263 |
| 318 | 7285 | 30.9 | 3653 |
319 rows × 3 columns
# 倒数第一、二列
df.iloc[:, [-2,-1]]
| Total Households | Average Household Size | |
|---|---|---|
| 0 | 1 | 1.00 |
| 1 | 12971 | 4.40 |
| 2 | 11731 | 4.36 |
| 3 | 15642 | 4.22 |
| 4 | 22547 | 2.73 |
| ... | ... | ... |
| 314 | 9690 | 3.93 |
| 315 | 816 | 2.62 |
| 316 | 6469 | 2.92 |
| 317 | 103 | 2.53 |
| 318 | 1982 | 3.67 |
319 rows × 2 columns
这里选择 2-4 列,输入的却是 1:4。这和 Python 或者 NumPy 里面的切片操作非常相似。既然我们能定位行和列,那么只需要组合起来,我们就可以选择数据集中的任何数据了。
2. 基于标签名称选择
除了根据数字索引选择,还可以直接根据标签对应的名称选择。这里用到的方法和上面的 iloc 很相似,少了个 i 为 df.loc[]。
df.loc[] 可以接受的类型有:
- 单个标签。例如:2 或 'a',这里的 2 指的是标签而不是索引位置(从0开始)。
- 列表或数组包含的标签。例如:['A', 'B', 'C']。
- 切片对象。例如:'A':'E',注意这里和上面切片的不同之处,首尾都包含在内。
- 布尔数组。
- 可返回标签的函数或参数。
下面,我们来演示 df.loc[] 的用法。先选择前 3 行:
df.loc[0:2]
| Zip Code | Total Population | Median Age | Total Males | Total Females | Total Households | Average Household Size | |
|---|---|---|---|---|---|---|---|
| 0 | 91371 | 1 | 73.5 | 0 | 1 | 1 | 1.00 |
| 1 | 90001 | 57110 | 26.6 | 28468 | 28642 | 12971 | 4.40 |
| 2 | 90002 | 51223 | 25.5 | 24876 | 26347 | 11731 | 4.36 |
然后,选择 2-4 列:
df.loc[:, 'Total Population':'Total Males']
| Total Population | Median Age | Total Males | |
|---|---|---|---|
| 0 | 1 | 73.5 | 0 |
| 1 | 57110 | 26.6 | 28468 |
| 2 | 51223 | 25.5 | 24876 |
| 3 | 66266 | 26.3 | 32631 |
| 4 | 62180 | 34.8 | 31302 |
| ... | ... | ... | ... |
| 314 | 38158 | 28.4 | 18711 |
| 315 | 2138 | 43.3 | 1121 |
| 316 | 18910 | 32.4 | 9491 |
| 317 | 388 | 44.5 | 263 |
| 318 | 7285 | 30.9 | 3653 |
319 rows × 3 columns
最后,选择 1,3 行和 Median Age 后面的列:
df.loc[[0, 2], 'Median Age':]
| Median Age | Total Males | Total Females | Total Households | Average Household Size | |
|---|---|---|---|---|---|
| 0 | 73.5 | 0 | 1 | 1 | 1.00 |
| 2 | 25.5 | 24876 | 26347 | 11731 | 4.36 |