NumPy 博客总结:
《Python数据分析基础教程:NumPy学习指南(第2版)》所有章节阅读笔记+代码
数据分析处理库Pandas
Pandas工具包是专门用作数据处理和分析的,其底层的计算其实都是由Numpy来完成,再把复杂的操作全部封装起来,使其用起来十分高效、简洁。在数据科学领域,无论哪个方向都是跟数据打交道,所以Pandas工具包是非常实用的。
1 数据预处理
导入pandas并查看版本:
1
2
import pandas as pd
pd.__version__
1.1 数据读取
打开一份真实数据集:泰坦尼克号乘客信息
1
2
df = pd.read_csv("./titanic_train.csv")
df.head()
.csv
文件是以逗号为分隔符的。还记得用NumPy读取数据的时候:
1 2 3 import numpy as np data = np.loadtxt("./titanic_train.csv", delimiter=",", usecols=(1, ),dtype = float, skiprows = 1) data这个
delimiter
参数就是分隔符的指定。
read_csv()
函数可以设置的参数非常多,也可以自己定义分隔符,给每列数据指定名字
如果想展示更多的数据,则可以在head()
函数中指定数值,例如df.head(10)
表示展示其中前10条数据:
1
df.head(10)
也可以展示最后几条数据:
1
df.tail()
1.2 DataFrame结构
DataFrame 是 Pandas 库的核心数据结构,可以被认为是一个增强版的表格,类似于 Excel 电子表格或 SQL 数据表。
- DataFrame的核心概念
- DataFrame 以二维表格的形式存储数据,包含行和列。
- 行索引 (Index): DataFrame 的每一行都有一个唯一的标签,称为行索引。 默认情况下,Pandas 会自动生成一个从 0 开始的整数索引。 也可以使用其他数据类型作为行索引,例如日期、字符串等。
- 列标签 (Column Names): DataFrame 的每一列都有一个名称,称为列标签。 列标签通常是字符串,用于标识每一列的含义。
- DataFrame 的每一列可以包含不同的数据类型,例如整数、浮点数、字符串、布尔值等。 这是与 NumPy 数组的主要区别之一,NumPy 数组要求所有元素具有相同的数据类型。
- 可以轻松地添加或删除 DataFrame 的行和列。
- DataFrame主要组成部分
- data: 实际存储的数据,可以是一个 NumPy 数组、Python 字典、列表或其他 DataFrame。
- index: 行索引,用于标识每一行。
- columns: 列标签,用于标识每一列。
补充:DataFrame的创建
有多种方法可以创建 DataFrame,以下是一些常用的方法:
1.从 NumPy 数组创建:
1
2
3
4
5
n = np.arange(9).reshape(3, 3)
display(n)
df = pd.DataFrame(n, index=["A", "B", "C"], columns=["D", "E", "F"])
display(df)
如果不指定index和columns就是:
1
2
df2 = pd.DataFrame(n)
display(df2)
2.从 Python 字典创建:
1
2
3
dic = {"1": [1, 2, 3], "2": [4, 5, 6]}
df = pd.DataFrame(dic, index=["A", "B", "C"])
display(df)
3.从列表创建:
1
2
3
4
data = [{'X': 1, 'Y': 2, 'Z': 3}, {'X': 4, 'Y': 5, 'Z': 6}, {'X': 7, 'Y': 8, 'Z': 9}]
display(data)
df = pd.DataFrame(data, index=["A", "B", "C"])
display(df)
4.从 CSV 文件创建:例如签名读取./titanic_train.csv
的例子。
补充:DataFrame的常用操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
选择数据:
df['column_name']: 选择一列。
df[['column1', 'column2']]: 选择多列。
df.loc['row_index']: 选择一行 (基于行索引)。
df.iloc[row_number]: 选择一行 (基于行号)。
df.loc['row_index', 'column_name']: 选择单个单元格。
df.iloc[row_number, column_number]: 选择单个单元格。
df[df['column_name'] > 10]: 使用条件选择行。---->此处注意不是第十行,是指单元格中数值大于10的!!!
添加/删除列:
df['new_column'] = [values]: 添加新列。
del df['column_to_delete']: 删除列。
df.drop('column_to_delete', axis=1): 删除列(返回新 DataFrame,不修改原始 DataFrame)。
添加/删除行:
df.loc['new_row'] = [values]: 添加新行。
df.drop('row_to_delete'): 删除行。
数据清洗和转换:
df.fillna(value): 填充缺失值。
df.dropna(): 删除包含缺失值的行。
df['column'].astype(dtype): 改变列的数据类型。
df['column'].apply(function): 对列中的每个元素应用一个函数。
分组和聚合:
df.groupby('column').mean(): 按列分组,并计算每组的平均值。
df.groupby('column').agg(['sum', 'mean', 'count']): 按列分组,并计算每组的总和、平均值和计数。
排序:
df.sort_values(by='column'): 按列排序。
例如:
1
df.loc[0]
1
df.loc[0, "Ticket" ]
1
df[df["Fare"] > 10]
1
2
# 添加/删除行
df1.loc[892] = [1, 0, 3, "Harisson test", "male", 22.0, 1, 0, "A/S 21189", 9.80, "NaN", "S"]
1
df1.drop(892)
1
2
3
# del df['column_to_delete']: 删除列。
del df1["Age"]
df1
数据清洗和转换:
df.fillna(value)
函数用于填充 DataFrame 中的缺失值(NaN, Not a Number)。 这是一个非常常用的数据清洗操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# df.fillna(value): 填充缺失值。
# 创建一个包含缺失值的 DataFrame
data = {'col1': [1, 2, np.nan, 4, 5],
'col2': [np.nan, 'B', 'C', 'D', np.nan],
'col3': [True, False, True, np.nan, False]}
df = pd.DataFrame(data)
display(df)
# 1. 使用标量值填充
df_filled_zero = df.fillna(0)
print("\n使用 0 填充:\n", df_filled_zero)
# 2. 使用不同的值填充不同的列
df_filled_dict = df.fillna({'col1': -1, 'col2': 'Unknown', 'col3': False})
print("\n使用字典填充:\n", df_filled_dict)
# 3. 使用前一个值填充 (向前填充)
df_ffill = df.fillna(method='ffill') # 前一个没有值,那么还是NaN
print("\n向前填充 (ffill):\n", df_ffill)
# 4. 使用后一个值填充 (向后填充)
df_bfill = df.fillna(method='bfill')
print("\n向后填充 (bfill):\n", df_bfill)
# 5. 按行填充
df_row_fill = df.fillna(method='ffill', axis=1) # 使用同一行前一个值填充
print("\n按行填充:\n", df_row_fill)
# 6. 限制填充的数量
df_limited = df.fillna(0, limit=1) # 每列最多填充一个缺失值
print("\n限制填充数量 (limit=1):\n", df_limited)
# 7. 修改原始 DataFrame
df.fillna(0, inplace=True) # 直接修改 df
print("\n修改后的原始 DataFrame (inplace=True):\n", df)
回答这个./titanic_train.csv
数据集,查看这个csv的信息:
1
df.info()
首先打印出来的是pandas.core.frame.DataFrame
,表示当前得到结果的格式是DataFrame,看起来比较难以理解,暂且把它当作是一个二维矩阵结构就好,其中,行表示数据样本,列表示每一个特征指标。
df.info()
函数用于打印当前读取数据的部分信息,包括数据样本规模、每列特征类型与个数、整体的内存占用等。
注:通常读取数据之后都习惯用
.info()
看一看其基本信息,以对数据有一个整体印象。
DataFrame能调用的属性还有很多,下面列举几种
1
df.index # 查看索引
拿到每一列的名字:
1
df.columns
每一列的类型:object表示的是Python的字符串
1
df.dtypes
直接取得数值矩阵:
1
df.values
1.3 数据索引
在数据分析过程中,如果想取其中某一列指标,该怎么办呢?以前可能会用到列索引,现在更方便了——指定名字即可:
1
2
age = df["Age"]
age[:5]
在DataFrame中可以直接选择数据的列名,但是什么时候指定列名呢?在读取数据时,read_csv()
函数会默认把读取数据中的第一行当作列名,打开csv文件观察一下。
如果想对其中的数值进行操作,则可以把其结果单独拿出来:
1
2
display(age)
age.values[:5]
这个结果跟Numpy很像啊,原因很简单,就是Pandas中很多计算和处理的底层操作都是由Numpy来完成的。
读取完数据之后,最左侧会加入一列数字,这些在原始数据中是没有的,相当于给样本加上索引了,如图所示。
默认情况下都是用数字来作为索引,但是这份数据中已经有乘客的姓名信息,可以将姓名设置为索引,也可以自己设置其他索引。
设置索引:
1
df.set_index("PassengerId")
特别注意一个坑:df.set_index("Name")
操作并没有直接修改原始 DataFrame df,除非你设置了 inplace=True
。 Pandas 中的许多操作,包括 set_index
,默认返回一个新的 DataFrame,而不是修改原始的 DataFrame。
1
2
df = df.set_index("Name")
print(df.index)
此时索引就变成每一个乘客的姓名(上述输出结果只截取了部分指标)。如果想得到某个乘客的特征信息,可以直接通过姓名来查找,是不是方便很多?
1
2
age = df["Age"]
age['Allen, Mr. William Henry']
如果要通过索引来取某一部分具体数据,最直接的方法就是告诉它取哪列的哪些数据:
1
df[['Age', "Fare"]][:5]
Pandas在索引中还有两个特别的函数用来帮忙找数据,简单概述一下。
(1).iloc()
:用位置找数据。
1
2
# 拿到第一个数据,索引从0开始的
df.iloc[0]
注意是
[]
,而不是()
使用切片拿一部分数据:
1
df.iloc[0:5]
不仅可以指定样本,还可以指定特征:
1
df.iloc[0:5,0:3] # 不仅仅指定哪几行,而且指定哪几列,第 0 行到第 4 行(包括第 0 行和第 4 行),第 0 列到第 2 列(包括第 0 列和第 2 列)
以上就是iloc()
用具体位置来取数的基本方法。
(2).loc()
:用标签找数据。如果使用loc()
操作,还可以玩得更个性一些:
1
2
print(df.index)
df.loc["Allen, Mr. William Henry"] # 直接通过名字标签来取数据
取数据的某一列信息:
1
2
# 取数据的某一列信息:
print(df.loc["Allen, Mr. William Henry", "Fare"])
也可以选择多个样本,多个特征:
1
df.loc['Heikkinen, Miss. Laina':'Allen, Mr. William Henry', :] # 最后这个“:”表示所有特征
如果要对数据进行赋值,操作也是一样的,找到它然后赋值即可:
例如,将Heikkinen, Miss. Laina的Fare设置为1000:
1
2
df.loc['Heikkinen, Miss. Laina','Fare'] = 1000
df.loc['Heikkinen, Miss. Laina']
在Pandas中bool类型同样可以当作索引:
1
df['Fare'] > 50
再例如:展示前5条:
1
df[df["Fare"] > 50][:5]
选择性别是男性的,但是只展示前5条:
1
df[df['Sex']=="male"][:5]
计算所有男乘客的平均年龄:
1
df.loc[df['Sex']=='male',"Age"].mean()
大于70岁的乘客有多少人:
1
(df['Age'] > 70).sum()
可以看到在数据分析中使用bool类型索引还是非常方便的,上述列举的几种方法也是Pandas中最常使用的。
1.4 创建DataFrame
DataFrame是通过读取数据得到的,如果想展示某些信息,也可以自己创建。例如下面这样的:
通过下面代码创建:
1
2
3
data = {'country':["China", "America", "India"], "population":[14, 3, 12]}
df = pd.DataFrame(data)
display(df)
最简单的方法就是创建一个字典结构,其中key表示特征名字,value表示各个样本的实际值,然后通过pd.DataFrame()
函数来创建。
在使用Notebook执行代码的时候,肯定发现了一件事,如果数据量过多,读取的数据不会全部显示,而是会隐藏部分数据,这时可以通过设置参数来控制显示结果(函数pd.set_option()
)。如果想详细了解各种设置方法,可以查阅其文档,里面有详细的解释。
千万不要硬背这些函数,它们只是工具,用的时候再查完全来得及。
下面总结4个常用的:
1.显示所有的行:
1
pd.set_option('display.max_rows', None)
注:也可以获得当前的display.max_rows
参数:
1
pd.get_option('display.max_rows')
再看一个例子:
1
2
pd.set_option('display.max_rows', 6) # 最大显示行数设置为6
pd.Series(index=range(1, 100))
2.显示所有的列:
1
pd.set_option('display.max_columns', None)
3.显示列中单独元素的最大长度
1
pd.set_option('max_colwidth', None)
4.换行显示、每行最大显示宽度
这个操作,需要几行代码配合操作。其中:
pd.set_option(‘expand_frame_repr’,True)
:True表示列可以换行显示。设置成False的时候不允许换行显示;pd.set_option(‘display.max_columns’, None)
:显示所有列;pd.set_option(‘display.width’, 80)
:横向最多显示多少个字符;
1
2
3
4
5
pd.set_option('expand_frame_repr', True)
pd.set_option('display.max_columns', None)
pd.set_option('display.width', 80)
df = pd.DataFrame(np.random.rand(3, 30), columns=list(range(30)))
print(df)
注:可能是jupyter notebook显示输出的特殊性,上述代码,如果将这个print()函数去掉,直接使用df显示输出,你会发现,换行显示没用。但是在Pycharm中,就不用担心这个问题了,因为必须print()
输出。
1.5 Series操作
pd.Series
是 Pandas 库中的一个核心数据结构,它是一个带标签的一维数组。 你可以把它想象成一个只有一列的 Excel 表格,或者是一个 NumPy 数组加上一个索引。 它是 DataFrame 的基本组成部分,DataFrame 可以看作是由多个 Series 组成的。
简单来说,读取的数据都是二维的,也就是DataFrame;如果在数据中单独取某列数据,那就是Series格式了,相当于DataFrame是由Series组合起来得到的,因而更高级一些。
补充:Series 的关键特性
- 数据 (Data): Series 存储实际的数据,可以是任何 NumPy 支持的数据类型 (整数、浮点数、字符串、布尔值等)。 所有的元素必须是相同的数据类型。
- 索引 (Index): Series 拥有一个索引,用于标识每个数据元素。 索引可以是:
- 整数 (默认): Pandas 会自动创建一个从 0 开始的整数索引。
- 字符串: 可以使用字符串作为索引,例如日期、名称等。
- 日期时间: 可以使用日期时间作为索引,用于时间序列分析。
- 其他任何可哈希的对象。
- 带标签的一维数组: 数据和索引共同构成了 Series 的结构。 通过索引,可以方便地访问和操作 Series 中的数据。
补充:创建 Series
有多种方法可以创建 Series,以下是一些常用的方法:
1.NumPy数组创建:
1
2
3
data = np.arange(1, 6)
s = pd.Series(data)
display(s)
2.从python列表创建:
1
2
3
list1 = ["A", "B", "C"]
s = pd.Series(list1)
s
3.从字典创建:字典的键变成了 Series 的索引。
1
2
3
data = {'A': 10, 'B': 20, 'C': 30, 'D': 40, 'E': 50}
s = pd.Series(data)
s
4.指定索引:
1
2
3
4
5
data = [10, 20, 30, 40, 50]
index = ['A', 'B', 'C', 'D', 'E']
s = pd.Series(data, index = index)
s
补充:Series 的常用操作
-
访问数据:
-
s[0]
: 通过整数位置访问 (与 NumPy 数组类似)。-
1 2
display(s) s[0]
-
- 上面有个警告:
s[0]
访问 Series 元素的方式 在未来的 Pandas 版本中可能会改变行为;建议使用s.iloc[pos]
来通过整数位置访问 Series 元素。
-
-
s['A']
: 通过索引标签访问。-
1 2
display(s) s["A"]
-
-
s[1:3]
: 切片 (基于整数位置)。-
1 2
display(s) s[1:3]
-
-
s['B':'D']
: 切片 (基于索引标签,包含结束标签)。-
1 2
display(s) s['B':'D']
-
-
s[[0, 2, 4]]
: 使用整数位置列表选择多个元素。-
1 2
display(s) s[[0, 2, 4]]
-
-
s[['A', 'C', 'E']]
: 使用索引标签列表选择多个元素。-
1 2
display(s) s[['A', 'C', 'E']]
-
-
s[s > 30]
: 使用布尔 Series 进行条件选择。-
1 2
display(s) s[s > 30]
-
-
-
属性:
-
s.index: 获取索引。
-
s.values: 获取数据 (NumPy 数组形式)。
-
s.dtype: 获取数据类型。
-
s.size: 获取 Series 的大小 (元素数量)。
-
s.name: 获取或设置 Series 的名称。
-
1 2 3 4 5 6 7
print(s.index) print(s.values) print(s.dtype) print(s.size) print(s.name) s.name = "test" s.name
-
-
方法:
- s.head(n): 返回前 n 行。
- s.tail(n): 返回后 n 行。
- s.describe(): 生成描述性统计信息 (均值、标准差、最小值、最大值等)。
- s.value_counts(): 统计每个值的出现次数。
- s.sort_values(): 按值排序。
- s.sort_index(): 按索引排序。
- s.apply(function): 对每个元素应用一个函数。
- s.map(dict_or_series): 使用字典或 Series 进行元素替换。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
data = [10, 20, 30, 40, 50]
index = ['A', 'B', 'C', 'D', 'E']
s = pd.Series(data, index = index)
display(s)
# - s.head(n): 返回前 n 行。
print("s.head(3)\n", s.head(3))
# - s.tail(n): 返回后 n 行。
print("s.tail(3)\n", s.tail(3))
# - s.describe(): 生成描述性统计信息 (均值、标准差、最小值、最大值等)。
print("s.describe()\n", s.describe())
# - s.value_counts(): 统计每个值的出现次数。
print("统计30出现的次数", s.value_counts(30))
# - s.sort_values(): 按值排序。
print("按值排序:\n", s.sort_values())
# - s.sort_index(): 按索引排序。
print("按索引排序:\n", s.sort_index())
# - s.apply(function): 对每个元素应用一个函数。
s_squared = s.apply(lambda x: x**2)
print("\n对s平方:\n", s_squared)
# - s.map(dict_or_series): 使用字典或 Series 进行元素替换。
# 创建一个 Series
s = pd.Series(['apple', 'banana', 'cherry', 'apple'])
print("原始 Series:\n", s)
# 创建一个映射字典
fruit_to_color = {'apple': 'red', 'banana': 'yellow', 'cherry': 'red'}
# 使用 map 函数进行映射
s_mapped = s.map(fruit_to_color)
print("\n映射后的 Series:\n", s_mapped)
Series 的用途:
- DataFrame 的列: DataFrame 的每一列都是一个 Series。
- 时间序列数据: 可以使用日期时间作为索引来表示时间序列数据。
- 统计分析: 用于存储和分析一维数据。
- 数据清洗: 用于转换和清理数据。
回到书上:
创建Series的方法也很简单:
上面这个Series的创建是:
1
2
3
4
data = [10, 11, 12]
index = ["a", "b", "c"]
s = pd.Series(data = data, index = index)
display(s)
其索引操作(查操作)也是完全相同的:(例如要得到上面s的11)
1
2
s.loc["b"]
s.iloc[1]
再来看看改操作:(将10改成100)
1
2
3
s1 = s.copy()
s1['a'] = 100
display(s1)
也可以使用replace()
函数:
1
2
s1.replace(to_replace=100, value=188, inplace=True)
display(s1)
注意,replace()
函数的参数中多了一项inplace,也可以试试将其设置为False,看看结果会怎样。之前也强调过,如果设置inplace=False
,就是不将结果赋值给变量,只相当于打印操作;如果设置inplace=True
,就是直接在数据中执行实际变换,而不仅是打印操作。
1
2
s1.replace(to_replace=100, value=199, inplace=False)
display(s1)
不仅可以改数值,还可以改索引:
1
2
3
print(s1.index)
s1.index = ["X", "Y", "Z"]
print(s1.index)
可以看到索引发生了改变,但是这种方法是按顺序来的,在实际数据中总不能一个个写出来吧?还可以用rename()函数,这样变换就清晰多了。
1
2
s1.rename(index={'X':"uu"}, inplace=True)
s1
接下来就是增操作了:
注:在 Pandas 的早期版本中,Series 对象确实有 append 方法,但它已被弃用,并在 Pandas 2.0 版本中被移除。
所以原书的
append
方法已经不可行了。
解决办法:使用 pd.concat 函数
1
2
3
4
5
data = [90, 80]
index = ["P", "Q"]
s2 = pd.Series(data = data, index = index)
s3 = pd.concat([s1, s2])
s3
增操作既可以把之前的数据增加进来,也可以增加新创建的数据。但是感觉增加完数据之后,索引有点怪怪的,既然数据重新组合到一起了,也应该把索引重新制作一下,可以在concat函数中指定ignore_index=True
参数来重新设置索引,结果如下:
1
2
s4 = pd.concat([s1, s2], ignore_index=True)
s4
最后还剩下删操作,最简单的方法是直接del选中数据的索引:
1
2
3
4
del s3["P"]
display(s3)
s3.drop(['uu', 'Y'], inplace=True)
display(s3)
给定索引就可以把这条数据删除,也可以直接删除整列,方法相同。