pandas(1)数据预处理

pandas 第一部分:数据预处理

Posted by Hilda on February 21, 2025

NumPy 博客总结:

《Python数据分析基础教程:NumPy学习指南(第2版)》所有章节阅读笔记+代码

70道NumPy 面试题(题目+答案)


数据分析处理库Pandas

Pandas工具包是专门用作数据处理和分析的,其底层的计算其实都是由Numpy来完成,再把复杂的操作全部封装起来,使其用起来十分高效、简洁。在数据科学领域,无论哪个方向都是跟数据打交道,所以Pandas工具包是非常实用的。

1 数据预处理

导入pandas并查看版本:

1
2
import pandas as pd
pd.__version__

image-20250221160914419

1.1 数据读取

打开一份真实数据集:泰坦尼克号乘客信息

image-20250221161130447

1
2
df = pd.read_csv("./titanic_train.csv")
df.head()

image-20250221161304735

.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)

image-20250221161729134

也可以展示最后几条数据:

1
df.tail()

image-20250221161752643

1.2 DataFrame结构

DataFrame 是 Pandas 库的核心数据结构,可以被认为是一个增强版的表格,类似于 Excel 电子表格或 SQL 数据表。

  1. DataFrame的核心概念
    1. DataFrame 以二维表格的形式存储数据,包含行和列。
    2. 行索引 (Index): DataFrame 的每一行都有一个唯一的标签,称为行索引。 默认情况下,Pandas 会自动生成一个从 0 开始的整数索引。 也可以使用其他数据类型作为行索引,例如日期、字符串等。
    3. 列标签 (Column Names): DataFrame 的每一列都有一个名称,称为列标签。 列标签通常是字符串,用于标识每一列的含义。
    4. DataFrame 的每一列可以包含不同的数据类型,例如整数、浮点数、字符串、布尔值等。 这是与 NumPy 数组的主要区别之一,NumPy 数组要求所有元素具有相同的数据类型。
    5. 可以轻松地添加或删除 DataFrame 的行和列。
  2. DataFrame主要组成部分
    1. data: 实际存储的数据,可以是一个 NumPy 数组、Python 字典、列表或其他 DataFrame。
    2. index: 行索引,用于标识每一行。
    3. 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)

image-20250221162535740

如果不指定index和columns就是:

1
2
df2 = pd.DataFrame(n)
display(df2)

image-20250221162623508

2.从 Python 字典创建:

1
2
3
dic = {"1": [1, 2, 3], "2": [4, 5, 6]}
df = pd.DataFrame(dic, index=["A", "B", "C"])
display(df)

image-20250221162822968

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)

image-20250221162951832

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]

image-20250221164316101

1
df.loc[0, "Ticket" ]

image-20250221164331434

1
df[df["Fare"] > 10]

image-20250221164345983

1
2
# 添加/删除行
df1.loc[892] = [1, 0, 3, "Harisson test", "male", 22.0, 1, 0, "A/S 21189", 9.80, "NaN", "S"]

image-20250221164202179

1
df1.drop(892)

image-20250221164242545

1
2
3
# del df['column_to_delete']: 删除列。
del df1["Age"]
df1

image-20250221164440101

数据清洗和转换:

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)

image-20250221165019191


回答这个./titanic_train.csv数据集,查看这个csv的信息:

1
df.info()

image-20250221165253769

首先打印出来的是pandas.core.frame.DataFrame,表示当前得到结果的格式是DataFrame,看起来比较难以理解,暂且把它当作是一个二维矩阵结构就好,其中,行表示数据样本,列表示每一个特征指标。

df.info()函数用于打印当前读取数据的部分信息,包括数据样本规模、每列特征类型与个数、整体的内存占用等。

注:通常读取数据之后都习惯用.info()看一看其基本信息,以对数据有一个整体印象。

DataFrame能调用的属性还有很多,下面列举几种

1
df.index  # 查看索引

image-20250221165506154

拿到每一列的名字:

1
df.columns

image-20250221165542406

每一列的类型:object表示的是Python的字符串

1
df.dtypes

image-20250221165629280

直接取得数值矩阵:

1
df.values

image-20250221165709081

1.3 数据索引

在数据分析过程中,如果想取其中某一列指标,该怎么办呢?以前可能会用到列索引,现在更方便了——指定名字即可:

1
2
age = df["Age"]
age[:5]

image-20250221165908166

在DataFrame中可以直接选择数据的列名,但是什么时候指定列名呢?在读取数据时,read_csv()函数会默认把读取数据中的第一行当作列名,打开csv文件观察一下。

如果想对其中的数值进行操作,则可以把其结果单独拿出来:

1
2
display(age)
age.values[:5]

image-20250221170014017

这个结果跟Numpy很像啊,原因很简单,就是Pandas中很多计算和处理的底层操作都是由Numpy来完成的

读取完数据之后,最左侧会加入一列数字,这些在原始数据中是没有的,相当于给样本加上索引了,如图所示。

image-20250221170056055

默认情况下都是用数字来作为索引,但是这份数据中已经有乘客的姓名信息,可以将姓名设置为索引,也可以自己设置其他索引。

设置索引:

1
df.set_index("PassengerId")

image-20250221170143258

特别注意一个坑:df.set_index("Name") 操作并没有直接修改原始 DataFrame df,除非你设置了 inplace=True。 Pandas 中的许多操作,包括 set_index,默认返回一个新的 DataFrame,而不是修改原始的 DataFrame。

1
2
df = df.set_index("Name")
print(df.index)

image-20250221170819000

image-20250221170831229

此时索引就变成每一个乘客的姓名(上述输出结果只截取了部分指标)。如果想得到某个乘客的特征信息,可以直接通过姓名来查找,是不是方便很多?

1
2
age = df["Age"]
age['Allen, Mr. William Henry']

image-20250221170854359

如果要通过索引来取某一部分具体数据,最直接的方法就是告诉它取哪列的哪些数据:

1
df[['Age', "Fare"]][:5]

image-20250221170955318

Pandas在索引中还有两个特别的函数用来帮忙找数据,简单概述一下。

(1).iloc():用位置找数据。

1
2
# 拿到第一个数据,索引从0开始的
df.iloc[0]

注意是[],而不是()

image-20250221171133129

使用切片拿一部分数据:

1
df.iloc[0:5]

image-20250221171218230

不仅可以指定样本,还可以指定特征:

1
df.iloc[0:5,0:3]   # 不仅仅指定哪几行,而且指定哪几列,第 0 行到第 4 行(包括第 0 行和第 4 行),第 0 列到第 2 列(包括第 0 列和第 2 列)

image-20250221171359987

以上就是iloc()用具体位置来取数的基本方法。 (2).loc():用标签找数据。如果使用loc()操作,还可以玩得更个性一些:

1
2
print(df.index)
df.loc["Allen, Mr. William Henry"] # 直接通过名字标签来取数据

image-20250221171631194

取数据的某一列信息:

1
2
# 取数据的某一列信息:
print(df.loc["Allen, Mr. William Henry", "Fare"])

image-20250221171738467

也可以选择多个样本,多个特征:

1
df.loc['Heikkinen, Miss. Laina':'Allen, Mr. William Henry', :] # 最后这个“:”表示所有特征

image-20250221171911602

如果要对数据进行赋值,操作也是一样的,找到它然后赋值即可:

例如,将Heikkinen, Miss. Laina的Fare设置为1000:

1
2
df.loc['Heikkinen, Miss. Laina','Fare'] = 1000
df.loc['Heikkinen, Miss. Laina']

image-20250221172032723

在Pandas中bool类型同样可以当作索引:

1
df['Fare'] > 50

image-20250221172122263

再例如:展示前5条:

1
df[df["Fare"] > 50][:5]

image-20250221172221432

选择性别是男性的,但是只展示前5条:

1
df[df['Sex']=="male"][:5]

image-20250221172335707

计算所有男乘客的平均年龄:

1
df.loc[df['Sex']=='male',"Age"].mean()

image-20250221172520049

大于70岁的乘客有多少人:

1
(df['Age'] > 70).sum()

image-20250221172644061

可以看到在数据分析中使用bool类型索引还是非常方便的,上述列举的几种方法也是Pandas中最常使用的。

1.4 创建DataFrame

DataFrame是通过读取数据得到的,如果想展示某些信息,也可以自己创建。例如下面这样的:

image-20250221172747028

通过下面代码创建:

1
2
3
data = {'country':["China", "America", "India"], "population":[14, 3, 12]}
df = pd.DataFrame(data)
display(df)

image-20250221172934498

最简单的方法就是创建一个字典结构,其中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))

image-20250221213436183

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)

image-20250221213023442

注:可能是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)

image-20250221213856618

2.从python列表创建:

1
2
3
list1 = ["A", "B", "C"]
s = pd.Series(list1)
s

image-20250221214002997

3.从字典创建:字典的键变成了 Series 的索引。

1
2
3
data = {'A': 10, 'B': 20, 'C': 30, 'D': 40, 'E': 50}
s = pd.Series(data)
s

image-20250221214054828

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

image-20250221214152976

补充:Series 的常用操作

  • 访问数据:

    • s[0]: 通过整数位置访问 (与 NumPy 数组类似)。

      • 1
        2
        
        display(s)
        s[0]
        
      • image-20250221214320948

      • 上面有个警告:s[0] 访问 Series 元素的方式 在未来的 Pandas 版本中可能会改变行为;建议使用 s.iloc[pos] 来通过整数位置访问 Series 元素。
    • s['A']: 通过索引标签访问。

      • 1
        2
        
        display(s)
        s["A"]
        
      • image-20250221214500242
    • s[1:3]: 切片 (基于整数位置)。

      • 1
        2
        
        display(s)
        s[1:3]
        
      • image-20250221214543321
    • s['B':'D']: 切片 (基于索引标签,包含结束标签)。

      • 1
        2
        
        display(s)
        s['B':'D']
        
      • image-20250221214619887
    • s[[0, 2, 4]]: 使用整数位置列表选择多个元素。

      • 1
        2
        
        display(s)
        s[[0, 2, 4]]
        
      • image-20250221214700358
    • s[['A', 'C', 'E']]: 使用索引标签列表选择多个元素。

      • 1
        2
        
        display(s)
        s[['A', 'C', 'E']]
        
      • image-20250221214731037
    • s[s > 30]: 使用布尔 Series 进行条件选择。

      • 1
        2
        
        display(s)
        s[s > 30]
        
      • image-20250221214804188
  • 属性:

    • 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
      
    • image-20250221220917836
  • 方法:

    • 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)

image-20250221221904651

image-20250221221916131


Series 的用途:

  • DataFrame 的列: DataFrame 的每一列都是一个 Series。
  • 时间序列数据: 可以使用日期时间作为索引来表示时间序列数据。
  • 统计分析: 用于存储和分析一维数据。
  • 数据清洗: 用于转换和清理数据。

回到书上:

创建Series的方法也很简单:

image-20250221222036772

上面这个Series的创建是:

1
2
3
4
data = [10, 11, 12]
index = ["a", "b", "c"]
s = pd.Series(data = data, index = index)
display(s)

image-20250221222203829

其索引操作(查操作)也是完全相同的:(例如要得到上面s的11)

1
2
s.loc["b"]
s.iloc[1]

image-20250221222316584

再来看看改操作:(将10改成100)

1
2
3
s1 = s.copy()
s1['a'] = 100
display(s1)

image-20250221222406863

也可以使用replace()函数:

1
2
s1.replace(to_replace=100, value=188, inplace=True)
display(s1)

image-20250221222511420

注意,replace()函数的参数中多了一项inplace,也可以试试将其设置为False,看看结果会怎样。之前也强调过,如果设置inplace=False,就是不将结果赋值给变量,只相当于打印操作;如果设置inplace=True,就是直接在数据中执行实际变换,而不仅是打印操作。

1
2
s1.replace(to_replace=100, value=199, inplace=False)
display(s1)

image-20250221222623032

不仅可以改数值,还可以改索引:

1
2
3
print(s1.index)
s1.index = ["X", "Y", "Z"]
print(s1.index)

image-20250221222712274

可以看到索引发生了改变,但是这种方法是按顺序来的,在实际数据中总不能一个个写出来吧?还可以用rename()函数,这样变换就清晰多了。

1
2
s1.rename(index={'X':"uu"}, inplace=True)
s1

image-20250221222847188

接下来就是增操作了:

注:在 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

image-20250221223201387

增操作既可以把之前的数据增加进来,也可以增加新创建的数据。但是感觉增加完数据之后,索引有点怪怪的,既然数据重新组合到一起了,也应该把索引重新制作一下,可以在concat函数中指定ignore_index=True参数来重新设置索引,结果如下:

1
2
s4 = pd.concat([s1, s2], ignore_index=True)
s4

image-20250221223355138

最后还剩下删操作,最简单的方法是直接del选中数据的索引:

1
2
3
4
del s3["P"]
display(s3)
s3.drop(['uu', 'Y'], inplace=True)
display(s3)

image-20250221223605512

给定索引就可以把这条数据删除,也可以直接删除整列,方法相同。