跳转至

Hands-on data analysis--- 动手学数据分析


写在最前面

这门课程得主要目的是通过真实的数据,以实战的方式了解数据分析的流程和熟悉数据分析python的基本操作。知道了课程的目的之后,我们接下来我们要正式的开始数据分析的实战教学,完成kaggle上泰坦尼克的任务,实战数据分析全流程。 这里有两份资料需要大家准备:图书《Python for Data Analysis》第六章和 baidu.com &google.com(善用搜索引擎)

来自Github开源项目Github

作者仅对于学习中遇到的问题进行标注整理 供大家学习讨论

本实验数据集和资料可以私信作者, 鼓励有条件的同学自行到Github上查阅下载

一、这篇笔记你将学到什么?

这篇笔记是基于 Kaggle 泰坦尼克项目的数据,从 0 带你走一遍数据分析最开始的几步:数据加载 → 初步观察 → 简单保存与导出,同时熟悉 numpypandas 的常用操作。


1. 数据加载:把数据“请进来”

  • 如何导入 numpypandas 并在报错时自己安装库。
  • 使用相对路径和绝对路径加载本地 CSV 文件,搞清楚“当前工作目录”的概念。
  • 理解 pd.read_csv()pd.read_table() 的区别(默认分隔符不同),以及 .csv.tsv.xlsx 这几种常见数据格式应该如何读取。
  • 学会用 chunksize 按块读取大文件,明白为什么要“逐块读取”来节省内存。
  • 学会把英文表头替换为中文列名,并把“乘客ID”设置为索引,同时理解 inplace=True 是“原地修改”。

2. 初步观察:快速给数据做个“体检”

  • 使用 df.info() 了解数据的整体结构:行数、列数、每列缺失值情况、数据类型和内存占用。
  • 使用 head() / tail() 查看前几行、后几行,快速感受数据长什么样。
  • 使用 isnull() 判断缺失值,理解哪些东西会被视为“空”(比如 NaN、None、NaT),哪些不会(比如 0、空字符串)。
  • 延伸出更多观察维度的思路:描述性统计(describe)、类别分布(value_counts)、极端值和相关性等,为后续真正的数据分析打基础。

3. 保存数据:把修改结果“带走”

  • 学会把修改过列名、索引后的 DataFrame 导出为新的 CSV 文件。
  • 知道文件默认保存位置是当前工作目录,也能指定保存路径。
  • 知道如何通过设置编码解决中文乱码问题(如 utf-8gbk 等)。

1 第一章:数据加载

1.1 载入数据

数据集下载 https://www.kaggle.com/c/titanic/overview

1.1.1 任务一:导入numpy和pandas

Python
import numpy as np
import pandas as pd

【提示】如果加载失败,学会如何在你的python环境下安装numpy和pandas这两个库

1.1.2 任务二:载入数据

(1) 使用相对路径载入数据
(2) 使用绝对路径载入数据

Python
df = pd.read_csv('train.csv')
df.head(3)
Python
# "E:\pycharm\Python3_9\hands-on-data-analysis\第一单元项目集合\train.csv"
# 注意:Windows系统的路径中反斜杠\需要用双反斜杠\\或者斜杠/来表示,否则会报错

df = pd.read_table('E:\\pycharm\\Python3_9\\hands-on-data-analysis\\第一单元项目集合\\train.csv',sep=',')
df.head(3)

【提示】相对路径载入报错时,尝试使用os.getcwd()查看当前工作目录。

  • cwd 是 Current Working Directory 的缩写

【思考1.】知道数据加载的方法后,试试pd.read_csv()和pd.read_table()的不同,如果想让他们效果一样,需要怎么做?了解一下'.tsv'和'.csv'的不同,如何加载这两个数据集?

【总结】加载的数据是所有工作的第一步,我们的工作会接触到不同的数据格式(eg:.csv;.tsv;.xlsx),但是加载的方法和思路都是一样的,在以后工作和做项目的过程中,遇到之前没有碰到的问题,要多多查资料吗,使用google,了解业务逻辑,明白输入和输出是什么。

Python
import os
# 打印当前工作目录
print("当前工作目录:", os.getcwd())
  • 【思考1.】pd.read_csv()pd.read_table() 的区别,以及如何处理 .csv.tsv 文件。

1. pd.read_csv()pd.read_table() 的主要区别

这两个函数在 Pandas 中都用于从文本文件加载数据到 DataFrame 中,但它们被设计用来处理不同默认分隔符的文件。

特性 pd.read_csv() pd.read_table()
默认分隔符 (sep 参数) 逗号 (,) 制表符 (\t)
使用场景 主要用于 .csv 文件(Comma Separated Values)。 主要用于 .tsv 文件(Tab Separated Values)或通用文本文件
默认索引列 index_col=None index_col=None

在功能上,pd.read_csv() 是更常用的和推荐的。实际上,pd.read_table() 只是 pd.read_csv() 的一个包装器(Wrapper),它在内部调用 pd.read_csv() 并自动将分隔符设置为制表符。


如何让它们效果一样?

要让 pd.read_csv()pd.read_table() 读取同一个文件并产生相同的效果,你需要手动设置它们的分隔符 (sep) 参数,使其保持一致:

场景 A:读取逗号分隔的文件(.csv

函数 代码 说明
pd.read_csv() pd.read_csv('data.csv') 使用默认值:逗号
pd.read_table() pd.read_table('data.csv', sep=',') 手动指定分隔符为逗号
sep 是 Separator(分隔符)的缩写

场景 B:读取制表符分隔的文件(.tsv

函数 代码 说明
pd.read_csv() pd.read_csv('data.tsv', sep='\t') 手动指定分隔符为制表符
pd.read_table() pd.read_table('data.tsv') 使用默认值:制表符

2. .tsv.csv 的区别及加载方法

.csv.tsv 都是常用的纯文本数据存储格式,它们的主要区别在于字段(列)之间是如何被分隔开的。

什么是分隔符?

分隔符(Delimiter)是一种特殊的字符,用于将数据行中的不同字段分开。

文件后缀 全称 含义 分隔符
.csv Comma Separated Values 逗号分隔值 逗号 (,)
.tsv Tab Separated Values 制表符分隔值 制表符 (\t)

要读取 .xlsx 文件(Microsoft Excel 文件),需要使用 Pandas 配合一个专门处理 Excel 格式的引擎

用于读取 .xlsx 文件的 Pandas 函数是 pd.read_excel()



读取 .xlsx 文件的步骤
1. 安装必要的库

Pandas 自己无法直接处理 .xlsx 这种复杂格式的 Excel 文件,它需要一个后端库作为“引擎”来解析文件。最常用的库是 openpyxl

如果你还没有安装,需要在命令行或终端运行以下命令:

Bash
pip install pandas openpyxl
2. 使用 pd.read_excel()

安装完 openpyxl 后,你就可以在 Python 中使用 pd.read_excel() 函数来读取 .xlsx 文件了。

Python
import pandas as pd

# 假设你的文件名为 'my_data.xlsx'
file_path = 'my_data.xlsx'

# 使用 pd.read_excel() 读取文件
df = pd.read_excel(file_path)

print(df.head())

1.1.3 任务三:每1000行为一个数据模块,逐块读取

Python
chunker = pd.read_csv('train.csv', chunksize=100)
# chunksize=1000    关键参数: 指定每次读取文件时,要读取的行数。这里设置为 100行。

print(f"chunker 的类型是: {type(chunker)}")

# 2. 遍历 chunker
chunk_count = 0
for chunk in chunker:
    chunk_count += 1

    print("-" * 30)
    print(f"这是第 {chunk_count} 个数据块 (chunk)")
    print(f"数据块的类型是: {type(chunk)}")
    print(f"数据块的行数是: {len(chunk)}")
    print("\n数据块 (chunk) 的具体内容:")

    # 打印每个数据块的前几行,以展示它是 DataFrame
    print(chunk.head(3))

# 'chunker' 每次迭代都会返回一个包含 1000 行数据的 DataFrame
for chunk in chunker:
    # 这里的 'chunk' 就是一个标准的 Pandas DataFrame,但它只有 1000 行。

    # 可以在这里对这 1000 行数据进行计算、筛选或聚合等操作
    print(f"当前数据块的行数: {len(chunk)}")

    # 示例:计算每块数据的平均值
    average = chunk['Age'].mean()
    print(f"平均值: {average}")

    # 如果需要,可以将每块处理结果保存到外部列表或文件中
    # results.append(average)

输出结果如下:

Bash
chunker 的类型是: <class 'pandas.io.parsers.readers.TextFileReader'>
------------------------------
这是第 1 个数据块 (chunk)
数据块的类型是: <class 'pandas.core.frame.DataFrame'>
数据块的行数是: 100
数据块 (chunk) 的具体内容:
 ......

【思考1.】什么是逐块读取?为什么要逐块读取呢?

【提示】大家可以chunker(数据块)是什么类型?用for循环打印出来出处具体的样子是什么?

【思考1.】

  • chunker(数据块)是什么类型?用for循环打印出来出处具体的样子是什么?

chunker 的类型是:

上方程序有chunker迭代出的内容

  • 什么是逐块读取?为什么要逐块读取呢?

Chunks是一个使用 Pandas 库来读取大型 CSV 文件时非常高效的技巧。

核心作用是:“不要一次性把整个大文件加载到内存中,而是把它分成一个个小的‘数据块’(Chunks),可以逐块处理。”


chunker = pd.read_csv('train.csv', chunksize=100)

代码逐段解释
代码段 含义
pd.read_csv(...) 告知 Pandas 库,我们要读取一个 CSV 文件。
'train.csv' 指定要读取的文件名(假设这是一个很大的训练数据集文件)。
chunksize=1000 关键参数: 指定每次读取文件时,要读取的行数。这里设置为 1000 行
chunker = ... 将读取的结果赋值给一个名为 chunker 的变量。

chunksize 参数的作用和原理

当一个文件非常大(比如有几百万行,甚至几 GB)时,你的计算机内存可能不足以一次性存储整个文件,或者一次性加载会导致程序运行缓慢甚至崩溃。

设置了 chunksize 后,pd.read_csv() 就不会返回一个完整的 DataFrame,而是返回一个特殊的迭代器 (Iterator) 对象,我们在这里命名为 chunker

1. chunker 是什么?

chunker 不是一个完整的 DataFrame,它是一个可以被循环的对象。

你可以把它想象成一个**“文件搬运工”**。你告诉搬运工:“每次只给我搬 1000 行数据。”

2. 如何使用 chunker

你需要使用 for 循环来从 chunker 中逐块地取出数据,进行处理

3. 为什么使用分块读取?
  • 节省内存 (Memory Efficiency): 你的程序永远只在内存中处理 1000 行数据,而不是整个文件。
  • 处理超大文件 (Big Data): 即使文件大小超过了你电脑的可用内存,也可以完整地处理它。
  • 进度跟踪 (Progress Tracking): 可以清晰地知道处理数据的进度。

总而言之,chunker = pd.read_csv('train.csv', chunksize=1000) 的作用就是开启了分批加载大文件的模式,以提高内存使用效率和处理速度

1.1.4 任务四:将表头改成中文,索引改为乘客ID [对于某些英文资料,我们可以通过翻译来更直观的熟悉我们的数据]

PassengerId => 乘客ID
Survived => 是否幸存
Pclass => 乘客等级(½/3等舱位)
Name => 乘客姓名
Sex => 性别
Age => 年龄
SibSp => 堂兄弟/妹个数
Parch => 父母与小孩个数
Ticket => 船票信息
Fare => 票价
Cabin => 客舱
Embarked => 登船港口

Python
df = pd.read_csv('train.csv', names=['乘客ID','是否幸存','仓位等级','姓名','性别','年龄','兄弟姐妹个数','父母子女个数','船票信息','票价','客舱','登船港口'],index_col='乘客ID',header=0)
df.head()   # 查看数据前5行输出(默认),如果想要多查看几行可以对head()传参

【思考】所谓将表头改为中文其中一个思路是:将英文列名表头替换成中文。还有其他的方法吗?

将表头改成中文并将索引改为乘客ID,可以通过以下几种方式实现:

方法 1:在读取数据时直接指定列名和索引

使用 pd.read_csv()names 参数指定中文列名,同时通过 index_col 参数设置索引为乘客ID。

Python
df = pd.read_csv(
 'train.csv',
 names=['乘客ID', '是否幸存', '乘客等级', '乘客姓名', '性别', '年龄', '堂兄弟/妹个数', '父母与小孩个数', '船票信息', '票价', '客舱', '登船港口'],
 index_col='乘客ID',
 header=0  # 跳过原始表头
)
df.head()

方法 2:读取后修改列名和索引

先读取数据,再通过 columns 属性和 set_index() 方法修改列名和索引。

Python
df = pd.read_csv('train.csv')

# 修改列名
df.columns = ['乘客ID', '是否幸存', '乘客等级', '乘客姓名', '性别', '年龄', '堂兄弟/妹个数', '父母与小孩个数', '船票信息', '票价', '客舱', '登船港口']

# 设置索引
df = df.set_index('乘客ID')
df.head()

方法 3:使用字典映射替换列名

通过字典映射替换列名,适合列名较多且需要动态修改的情况。

Python
df = pd.read_csv('train.csv')

# 创建英文到中文的映射字典
column_mapping = {
 'PassengerId': '乘客ID',
 'Survived': '是否幸存',
 'Pclass': '乘客等级',
 'Name': '乘客姓名',
 'Sex': '性别',
 'Age': '年龄',
 'SibSp': '堂兄弟/妹个数',
 'Parch': '父母与小孩个数',
 'Ticket': '船票信息',
 'Fare': '票价',
 'Cabin': '客舱',
 'Embarked': '登船港口'
}

# 替换列名
df.rename(columns=column_mapping, inplace=True)

# 设置索引
df.set_index('乘客ID', inplace=True)
df.head()

方法 4:逐列修改列名

如果只需要修改部分列名,可以通过 rename() 方法逐列修改。

Python
df = pd.read_csv('train.csv')

# 修改部分列名
df.rename(columns={
 'PassengerId': '乘客ID',
 'Survived': '是否幸存'
}, inplace=True)

# 设置索引
df.set_index('乘客ID', inplace=True)
df.head()
  • 在上面的方法三、四中 inplace=Trues实现了什么功能

在 Pandas 中,inplace=True 是一个非常常见的参数,它的含义是:直接在原对象上进行修改,而不是返回一个新的对象。

简单来说,就是“原地修改”

详细对比
1. 默认情况 (inplace=False)

如果你不写这个参数,或者设置为 False(这是默认值),Pandas 不会改变原本的 df 变量,而是把修改后的结果作为一个新的 DataFrame 返回给你。你需要用变量去接收它。

Python
# ❌ 原来的 df 不会变
df.rename(columns=column_mapping) 

# ✅ 需要赋值给变量才能保存修改
df_new = df.rename(columns=column_mapping) 
# 或者覆盖原变量
df = df.rename(columns=column_mapping)
2. 使用 inplace=True

如果你设置为 True,Pandas 会直接修改内存中的 df 对象。这个操作没有返回值(返回 None),但 df 本身变了。

Python
# ✅ 原来的 df 直接被修改了
df.rename(columns=column_mapping, inplace=True)

# ❌ 千万不要这样写,因为 inplace=True 返回的是 None
# df = df.rename(columns=column_mapping, inplace=True)  <-- 这样 df 会变成 None
总结
  • inplace=False (默认)“给我一份改好的复印件,原件不要动。”(安全,适合链式调用)
  • inplace=True“直接在原件上改。”(省去赋值步骤,代码看起来简洁一些)

1.2 初步观察

导入数据后,你可能要对数据的整体结构和样例进行概览,比如说,数据大小、有多少列,各列都是什么格式的,是否包含null等

1.2.1 任务一:查看数据的基本信息

Python
df.info()
Text Only
<class 'pandas.core.frame.DataFrame'>
Index: 891 entries, 1 to 891
Data columns (total 11 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   是否幸存    891 non-null    int64  
 1   仓位等级    891 non-null    int64  
 2   姓名      891 non-null    object 
 3   性别      891 non-null    object 
 4   年龄      714 non-null    float64
 5   兄弟姐妹个数  891 non-null    int64  
 6   父母子女个数  891 non-null    int64  
 7   船票信息    891 non-null    object 
 8   票价      891 non-null    float64
 9   客舱      204 non-null    object 
 10  登船港口    889 non-null    object 
dtypes: float64(2), int64(4), object(5)
memory usage: 83.5+ KB
  • 输出结果解释

Pandas DataFrame 的 .info() 方法的运行结果,它提供了关于这个数据集(DataFrame)的简洁、概要的信息

我们可以将这些信息分为几个关键部分来理解:

1. 整体结构信息
  • <class 'pandas.core.frame.DataFrame'>
  • 含义: 确认这个对象是一个 Pandas DataFrame 类型,这是 Pandas 中最常用的二维(表格型)数据结构。
  • Int64Index: 891 entries, 1 to 891
  • 含义: 描述数据的行信息
    • 总行数 (Entries): 共有 891 条记录(或行)。
    • 索引 (Index): 索引类型是 64 位整数 (Int64Index),范围是从 1 到 891。这表明数据的索引是从 1 开始的,而不是默认的 0 开始。
  • Data columns (total 11 columns)
  • 含义: 描述数据的列信息
    • 总列数: 共有 11 列。

2. 列详细信息(数据质量检查的核心)

这部分是 .info() 输出最重要的部分,它详细列出了每一列的数据情况。

序号 列名 (Column) 非空值计数 (Non-Null Count) 数据类型 (Dtype) 缺失值情况
0 是否幸存 891 non-null int64 无缺失值 (891/891)
1 仓位等级 891 non-null int64 无缺失值 (891/891)
2 姓名 891 non-null object 无缺失值
3 性别 891 non-null object 无缺失值
4 年龄 714 non-null float64 有缺失值 (177 个)
5 兄弟姐妹个数 891 non-null int64 无缺失值
9 客舱 204 non-null object 严重缺失 (687 个)
10 登船港口 889 non-null object 有缺失值 (2 个)
关键数据类型解释:
  • int64 (Integer 64-bit): 64 位整数。通常用于存储整数数据(如计数、ID、等级)。
  • float64 (Float 64-bit): 64 位浮点数。用于存储小数数据(如年龄、票价)。
  • object 字符串(文本)类型。用于存储文本数据(如姓名、客舱号)。
缺失值分析:

通过比较总行数 (891) 和 Non-Null Count,我们可以迅速发现数据集中的 缺失数据 (Missing Values)

  • 年龄: 只有 714 个非空值,意味着有 \(891 - 714 = \mathbf{177}\) 个缺失值。
  • 客舱: 只有 204 个非空值,意味着有 \(891 - 204 = \mathbf{687}\) 个缺失值(缺失率非常高)。
  • 登船港口: 只有 889 个非空值,意味着有 \(\mathbf{2}\) 个缺失值。

这些缺失值需要在后续的数据清洗和预处理阶段进行处理(比如填充或删除)。


3. 资源使用信息
  • dtypes: float64(2), int64(4), object(5)
  • 含义: 总结了数据类型在整个 DataFrame 中的分布。
    • 有 2 列是 float64 类型。
    • 有 4 列是 int64 类型。
    • 有 5 列是 object(字符串)类型。
  • memory usage: 83.5+ KB
  • 含义: 估算这个 DataFrame 在内存中所占用的空间大约是 83.5 千字节 (KB)。这个数值可以帮助你评估处理更大文件时的内存需求。

总而言之,.info() 是进行初步数据探索数据质量检查时最重要的一步,它让你快速了解数据的规模、类型和完整性。

  • df.info 和 df.info()有什么区别

这是一个非常经典的 Python 初学者问题,触及了方法调用(Call)对象引用(Reference)的核心区别。

简单来说:带括号是“做动作”,不带括号是“看说明书”。

以下是详细对比分析:

1. df.info() —— 执行方法(做动作)

  • 含义:这是在调用(Call)这个函数。你告诉 Python:“现在立刻运行 info 这个功能。”
  • 结果:Python 会执行 info 内部的代码,计算并打印出 DataFrame 的详细摘要(行数、列数、非空值、内存占用等)。
  • 你在数据分析中需要的:正是这个。

代码示例:

Python
df.info()

输出结果(示例):

Text Only
<class 'pandas.core.frame.DataFrame'>
Int64Index: 891 entries, 1 to 891
Data columns (total 12 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   是否幸存      891 non-null    int64  
 ... (省略中间内容) ...
dtypes: float64(2), int64(5), object(5)
memory usage: 90.5+ KB

2. df.info —— 查看对象(看说明书)

  • 含义:这是在引用这个属性。你告诉 Python:“我想看看 df 对象里名为 info 的那个东西是什么。”
  • 结果:Python 不会运行代码,而是告诉你:“这是一个绑定在 DataFrame 上的方法对象。”
  • 你在数据分析中需要的:通常不需要,除非你在做高级编程(比如把这个函数赋值给另一个变量)。

代码示例:

Python
df.info

输出结果(示例):

Text Only
<bound method DataFrame.info of      是否幸存  仓位等级     姓名     性别     年龄  ...  父母子女个数            船票信息     票价    客舱  登船港口
乘客ID                                      ...                                         
1       0     3  Braund...  male   22.0  ...       0       A/5 21171   7.25   NaN     S
2       1     1  Cuming... female  38.0  ...       0        PC 17599  71.28   C85     C
...
[891 rows x 11 columns]>

(注意:它只是打印出了这个方法的描述信息,并没有统计数据的非空值或类型)


总结对比表

特性 df.info() (带括号) df.info (不带括号)
本质 函数调用 (Function Call) 对象引用 (Object Reference)
比喻 按下遥控器上的“播放”键 手指着遥控器上的“播放”键
作用 执行代码,计算并显示数据摘要 查看这个方法本身在内存中的样子
是否有返回值 有(通常打印到控制台或返回 None) 有(返回方法对象本身)
常用场景 99.9% 的情况(查看数据信息) 极少(调试或高阶函数式编程)

结论

在做数据分析查看数据概况时,请务必加上括号 ()

1.2.2 任务二:观察表格前10行的数据和后15行的数据

Python
df.head(10)
Python
df.tail(15)

记住相关语法就可以,这里就不展示结果了。

1.2.3任务三:判断数据是否为空,为空的地方返回True,其余地方返回False

Python
df.isnull().head()
# 展示前 5 行数据的缺失值分布情况(True 表示缺失)

.isnull()方法是查看空缺值 ,这里结合.head()方法一起使用。避免一次性输出过多

【总结】上面的操作都是数据分析中对于数据本身的观察

【思考】对于一个数据,还可以从哪些方面来观察?找找答案,这个将对下面的数据分析有很大的帮助

  • 对于一个数据,还可以从哪些方面来观察?

除了上述的的查看头尾行 (head/tail)、基本信息 (info) 和缺失值 (isnull) 之外,数据观察(Exploratory Data Analysis, EDA)通常还需要从以下几个核心维度进行深入:

1. 描述性统计分析 (Descriptive Statistics)

这是最直接了解数据数值分布的方法。

  • 方法df.describe()
  • 观察点
    • 均值 (mean) vs 中位数 (50%):如果两者相差很大,说明数据存在偏斜(Skewness)或异常值。
    • 标准差 (std):数据波动大不大?
    • 最小值 (min) / 最大值 (max):数据范围是否合理?(例如:年龄不应为负数,票价不应无穷大)。
Python
# 查看数值型列的统计摘要
df.describe()

2. 唯一值与计数 (Unique Values & Counts)

对于分类数据(如性别、登船港口、客舱等级),我们需要知道有哪些类别以及每个类别的数量。

  • 方法
    • df['列名'].unique():查看有哪些唯一值。
    • df['列名'].value_counts():查看每个类别有多少条数据。
  • 观察点
    • 是否存在拼写错误的类别(如 "male" 和 "Male")?
    • 样本是否均衡?(例如:幸存者和遇难者比例是否悬殊?)
Python
# 查看“性别”列有哪些值,以及各有多少人
df['性别'].value_counts()

3. 数据类型与格式检查 (Data Types & Format)

虽然 info() 给了大概的类型,但有时需要更细致的检查。

  • 观察点
    • 数值是否被存成了字符串?(例如票价列里混入了 "$" 符号导致变成 object 类型)。
    • 时间格式是否正确?(例如日期是 "2021-01-01" 字符串还是 datetime 对象)。

4. 异常值检测 (Outlier Detection)

寻找那些“格格不入”的数据。

  • 方法
    • 排序df.sort_values(by='列名'),看看最大和最小的几个值是否离谱。
    • 可视化(箱线图):虽然还没讲到绘图,但这是观察异常值最直观的方法。
  • 观察点
    • 比如泰坦尼克号数据中,是否有年龄超过 100 岁或票价高达 5000 的记录?

5. 相关性分析 (Correlation)

观察特征之间,或者特征与目标变量(是否幸存)之间的关系。

  • 方法df.corr()
  • 观察点
    • 票价越高,幸存率越高吗?
    • 年龄和兄弟姐妹数量有关联吗?

总结代码示例

你可以尝试运行以下代码来扩展你的观察视角:

Python
# 1. 统计摘要(快速看数值分布)
print("--- 统计摘要 ---")
print(df.describe())

# 2. 查看分类数据的分布(例如:查看男女比例)
print("\n--- 性别分布 ---")
print(df['性别'].value_counts())

# 3. 排序观察(例如:看看票价最高的前 5 个人)
print("\n--- 票价最高的 5 人 ---")
print(df.sort_values(by='票价', ascending=False).head(5))
  • .isnull()判断为空的机制是什么? 空值可能是没有填充 Nan None...

理解 Pandas 如何定义“空值”对于数据清洗至关重要。

1. df.isnull().head() 语句解释

这行代码是两个方法的链式调用,我们可以把它拆解来看:

  1. df.isnull()
    • 作用:它会遍历整个 DataFrame,判断每一个单元格是否为“空”。
    • 返回结果:返回一个与原 DataFrame 形状完全一样的新 DataFrame,但里面的内容变成了布尔值(Boolean):
      • 如果原位置是空值,显示 True
      • 如果原位置有数据,显示 False
  2. .head()
    • 作用:取前 5 行。
    • 目的:因为整个表可能很大,打印出来看不清,我们只看前 5 行的布尔值结果,快速检查一下哪些列可能存在缺失数据。

总结:这行代码的作用是“展示前 5 行数据的缺失值分布情况(True 表示缺失)”


2. Pandas 判断“空值”的机制

Pandas 在判断是否为 null 时,主要依据底层 NumPy 的标准以及 Python 的对象标准。

被判定为 True (是空值) 的情况:

Pandas 的 isnull() (以及它的别名 isna()) 会将以下情况视为缺失值:

  1. NaN (Not a Number)
    • 这是标准的浮点数缺失值(IEEE 754 标准)。
    • 这是 Pandas 中最常见的缺失值形式(数值列通常都用这个)。
  2. None (Python 的 None 对象)
    • Python 自带的“无”对象。
    • 在 Pandas 的 object 类型(字符串/混合类型)列中,None 会被保留并视为缺失值。
    • 在数值列中,Pandas 通常会自动把 None 转换为 NaN
  3. NaT (Not a Time)
    • 专门用于时间序列(Datetime)类型的缺失值。

被判定为 False (不是空值) 的情况(易错点):

很多初学者容易混淆以下情况,Pandas 不认为它们是空值:

  1. 空字符串 """ "
    • 注意:这是最容易踩坑的地方!在 Pandas 看来,空字符串是一个长度为 0 的有效字符串对象,不是 Null
  2. 字符串 "NaN""null"
    • 如果你读取 CSV 时没有正确解析,这些只是普通的文本字符,不是真正的缺失值。
  3. 数字 0
    • 0 是一个有效的数字,不是空。
  4. False
    • 布尔值的 False 是有效值。
  5. inf (无穷大)
    • 默认情况下,无穷大被视为有效值(可以通过配置修改)。

代码演示

Python
import pandas as pd
import numpy as np

data = {
    '数值': [1, np.nan, 0],             # np.nan 是空,0 不是
    '对象': ['abc', None, ''],          # None 是空,空字符串 '' 不是
    '时间': [pd.Timestamp('20210101'), pd.NaT, pd.Timestamp('20210102')] # NaT 是空
}

df_test = pd.DataFrame(data)

print("原始数据:")
print(df_test)
print("-" * 20)
print("isnull() 判断结果:")
print(df_test.isnull())

输出结果预演:

  • np.nan -> True
  • None -> True
  • pd.NaT -> True
  • 0 -> False
  • '' (空字符串) -> False

1.3 保存数据

1.3.1 任务一:将你加载并做出改变的数据,在工作目录下保存为一个新文件train_chinese.csv

Python
# 注意:不同的操作系统保存下来可能会有乱码。大家可以加入`encoding='GBK' 或者 ’encoding = ’utf-8‘‘`
df.to_csv('train_chinese.csv')

默认会将文件保存在当前的“工作目录”(Current Working Directory)下。

也可以 指定绝对路径保存 注意斜杠的问题

【总结】数据的加载以及入门,接下来就要接触数据本身的运算,我们将主要掌握numpy和pandas在工作和项目场景的运用。

复习:数据分析的第一步,加载数据我们已经学习完毕了。当数据展现在我们面前的时候,我们所要做的第一步就是认识他,今天我们要学习的就是了解字段含义以及初步观察数据

1 第一章:pandas基础

1.4 知道你的数据叫什么——Series 和 DataFrame 初认识

在这一部分,你会学到:

  • pandas 的两个核心数据结构:Series(一维)DataFrame(二维表格),理解它们分别长什么样、适合装什么数据。
  • 能自己写出简单示例:
  • 用字典创建 Series(如州名 → 数值映射)。
  • 用字典创建 DataFrame(如 state/year/pop 这样的表格数据)。
  • 重新加载 train.csv 或上一节保存好的 train_chinese.csv,并通过 df.columns 查看每一列的列名。
  • 学会两种访问列的方式,并理解它们的区别:
  • 字典访问法:df['Cabin']
  • 属性访问法:df.Cabin 知道什么时候必须用中括号索引(列名有空格、特殊字符、想用变量名等)。
  • 学会对比两个 DataFrame(如 traintest_1)的列差异:
  • 用集合运算找出“测试集中多出的列”。
  • 用多种方式删除多余列:deldrop(columns=...)pop() 或“只保留需要的列”的思路。
  • 理解 Unnamed: 0 这类列名出现的原因:通常是保存时把索引写进 CSV、读取时又没指定 index_col 导致的。
  • df.drop([...], axis=1) 实现“临时隐藏几列只看其他列”,并理解:
  • 不加 inplace=True 时,只是返回一个“修改后的视图/新对象”,原 df 不会被真正改动。
  • 何时适合用 inplace=True 做“原地修改”。

1.5 筛选的逻辑——用条件把数据“挑”出来

在这一部分,你会学到:

  • 利用布尔条件对 DataFrame 做行筛选:
  • 例如:df[df["Age"] < 10] 获取 10 岁以下乘客。
  • 构造多条件筛选,并用逻辑运算符组合条件:
  • 使用 & 表示“同时满足”(交集),| 表示“满足其一”(并集),~ 表示取反。
  • 理解为什么每个条件外面都必须加一层括号。
  • 完成“10 < Age < 50”的区间筛选,并将结果命名为 midage
  • 使用 reset_index() 重置筛选后 DataFrame 的索引:
  • 理解原索引可能是不连续、不是 0 开始的。
  • 明白 reset_index()reset_index(drop=True) 的区别:一个保留旧索引为列,一个直接丢弃。
  • 知道何时需要先重置索引,再按“行号感觉”的方式去定位行。
  • 使用 loc 按“标签”选取数据:
  • 例如选出 midage 中第 100 行指定列("Pclass""Sex")的数据。
  • 以及多行多列组合选择:loc[[100, 105, 108], ['Pclass', 'Name', 'Sex']]
  • 使用 iloc 按“位置”选取数据:
  • 例如用 iloc[[100,105,108],[2,3,4]] 通过“行号 + 列号”的方式选择相同的数据。
  • 通过对比 lociloc,理解它们的关键区别:
  • loc:按标签(行索引名、列名)选取。
  • iloc:按位置(整数下标)选取。
  • 切片区间在 loc 中是闭区间,在 iloc 中是左闭右开

1.4 知道你的数据叫什么

我们学习pandas的基础操作,那么上一节通过pandas加载之后的数据,其数据类型是什么呢?

1.4.1 任务一:pandas中有两个数据类型DataFrame和Series,通过查找简单了解他们。然后自己写一个关于这两个数据类型的小例子🌰[开放题]

Python
import numpy as np
import pandas as pd
Python
sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}
example_1 = pd.Series(sdata)
example_1
Python
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'],
        'year': [2000, 2001, 2002, 2001, 2002, 2003],
        'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
example_2 = pd.DataFrame(data)
example_2
  • Pandas 主要有两个核心数据结构 DataFrame和Series 的介绍

Pandas 主要有两个核心数据结构,以及存储在这些结构中的底层数据类型(dtypes)

1. 核心数据结构 (Data Structures)

这是 Pandas 中最常用的两个对象:

  • Series (序列)

  • 定义:一维数组对象。

  • 理解:可以把它看作是 Excel 表格中的一列,或者是一个带标签(索引)的 Python 列表。
  • 组成:由一组数据(Values)和一组与之相关的数据标签(Index)组成。
  • 代码示例(参考你的 example_1):
Python
# 创建 Series
s = pd.Series([1, 3, 5, np.nan, 6, 8])
  • DataFrame (数据框)

  • 定义:二维表格型数据结构。

  • 理解:可以把它看作是整个 Excel 表格或 SQL 表。
  • 组成:它既有行索引(Index),也有列索引(Columns)。你可以把它看作是由多个 Series 组成的字典(共用同一个行索引)。
  • 代码示例(参考你的 example_2):
Python
# 创建 DataFrame
df = pd.DataFrame({
    'A': 1.,
    'B': pd.Timestamp('20130102'),
    'C': pd.Series(1, index=list(range(4)), dtype='float32')
})
2. 底层数据类型 (Dtypes)

这是指存储在 Series 或 DataFrame 单元格中的具体数值类型。你可以通过 df.dtypes 查看。常见的包括:

  • object:通常代表字符串(String),或者是混合类型的数据。
  • int64:整数(Integer)。
  • float64:浮点数(Float),即带小数点的数字。
  • bool:布尔值(True/False)。
  • datetime64:日期时间类型。

1.4.2 任务二:根据上节课的方法载入"train.csv"文件

Python
df = pd.read_csv('./train.csv')
df.head(3)

也可以加载上一节课保存的"train_chinese.csv"文件。

1.4.3 任务三:查看DataFrame数据的每列的名称

Python
df.columns

1.4.4任务四:查看"Cabin"这列的所有值 [有多种方法]

Python
# 字典访问法
df['Cabin'].head(3)
Python
# 属性访问法 / 点号法
df.Cabin.head(3)
  • df['Cabin'].head(3)、 df.Cabin.head(3)这两种方法的区别

这两种方法在当前这个例子中(访问名为 "Cabin" 的列)效果是完全一样的,都返回该列的数据。

但是,它们在适用场景灵活性上有很大的区别。以下是详细对比:

1. df['Cabin'] (方括号法 / 字典访问法)

这是最通用、最推荐的方法。

  • 优点

  • 支持任意列名:如果列名包含空格(如 "First Name")、特殊符号(如 "Price($)")或数字开头,必须用这种方法。

  • 支持变量:可以使用变量来动态访问列。

    Python
    col_name = 'Cabin'
    df[col_name]  # 可行
    
  • 创建新列:必须用这种方法来添加新列。

    Python
    df['New_Column'] = 1  # 创建成功
    
  • 避免冲突:如果列名和 Pandas 的内置方法名重名(比如列名叫 count, sum, class),这种方法依然能准确获取列数据。

2. df.Cabin (属性访问法 / 点号法)

这是一种语法糖(Syntactic Sugar),主要是为了写起来方便。

  • 优点

    • 书写快捷:少打两个引号和两个括号。
    • 代码补全:在 Jupyter Notebook 或 IDE 中,输入 df. 后按 Tab 键通常能自动补全列名。
  • 缺点(局限性)

    • 不支持特殊列名:如果列名有空格(df.First Name ❌)或特殊字符,会报错。
    • 不能创建新列df.New_Column = 1 只会给 df 对象添加一个普通的 Python 属性,而不会在 DataFrame 数据结构中真正添加一列数据。
    • 容易冲突:如果你的列名叫做 shapeindexT 或者 count,使用 df.shape 访问的是 DataFrame 的形状属性,而不是名为 "shape" 的那一列数据。
总结建议
  • 日常快速查看数据且列名规范(无空格、非关键字)时,可以用 df.ColumnName
  • 编写正式脚本处理复杂列名创建新列时,请务必使用 df['ColumnName']

1.4.5 任务五:加载文件"test_1.csv",然后对比"train.csv",看看有哪些多出的列,然后将多出的列删除

手动打印表头(df.columns)进行肉眼对比

经过我们的观察发现一个测试集test_1.csv有一列是多余的,我们需要将这个多余的列删去

Python
test_1 = pd.read_csv('test_1.csv')
test_1.head(3)
Python
# 删除多余的列
del test_1['a']
test_1.head(3)

【思考】还有其他的删除多余的列的方式吗?

以下是三种删除多余列的方法

  1. 使用 drop() 函数(最推荐)这是最标准的方法,因为它不会像 del 那样直接修改原数据(除非你指定 inplace=True),这在数据处理管道中更安全。
Python
'''

#思考回答

# 方法 A:返回一个新的 DataFrame(不改变原数据)
test_1_clean = test_1.drop(['a'], axis=1)

# 方法 B:直接在原数据上修改(加上 inplace=True)
test_1.drop(['a'], axis=1, inplace=True)

# 方法 C:使用 columns 参数(更直观,不需要写 axis=1)
test_1.drop(columns=['a'], inplace=True)

# axis 参数用于指定操作的方向(行还是列)。
# axis=0 (默认):代表 行 (Rows/Index)。也就是纵向 ↓。
# axis=1:代表 列 (Columns)。也就是横向 →。

'''
  1. 使用 pop() 方法 这个方法会删除列,同时把被删除的列作为返回值弹出来。如果你需要把删掉的数据存起来备用,这个方法很有用。
Python
# 删除 'a' 列,并把它的数据赋值给 deleted_col
# deleted_col = test_1.pop('a')
  1. 列表切片/筛选(保留法)与其说是“删除”,不如说是“只保留想要的”。当你需要删除的列很多,而保留的列很少时,这种方法最快。
Python
# 只保留 'PassengerId' 和 'Survived',其他的都不要了
# test_1 = test_1[['PassengerId', 'Survived']]
总结对比
方法 特点 适用场景
del df['col'] Python 原生语法,直接修改原数据,无返回值。 快速删除单列,确定不再需要原数据。
df.drop() Pandas 专用,功能强大,支持多列,默认不修改原数据。 最通用,特别是需要删除多列或在链式调用中。
df.pop() 删除并返回该列数据。 删除的同时还需要用到这列数据。
df[['col1']] 筛选保留。 需要删除的列比保留的列多得多时。
补充:在Pandas 中如何高效、编程化地对比两个 DataFrame 的列差异?

使用 Python 的集合(Set)操作来快速找出不同之处。

方法:使用集合操作 (Set Operations)

集合操作可以让你瞬间找出:

  1. A 有但 B 没有的列(差集)
  2. B 有但 A 没有的列(差集)
  3. 两者共有的列(交集)
  4. 两者所有的列(并集)

假设你有两个 DataFrame:traintest_1

Python
# 获取列名集合
train_cols = set(df.columns)
test_cols = set(test_1.columns)

# 1. 找出 test_1 中多出的列 (test_1 有但 train 没有)
diff = test_cols - train_cols
print(f"test_1 多出的列: {diff}")

# 2. 找出 train 中多出的列 (train 有但 test_1 没有)
# diff_2 = train_cols - test_cols
# print(f"train 多出的列: {diff_2}")
为什么这种方法更高效?
  • 自动化:不需要人工逐个单词去核对,避免眼花看错。
  • 直接可用:结果是一个集合或列表,你可以直接把这个结果传给 drop() 函数来删除多余的列。

这里不显示多余的列是因为 如果按顺序执行该程序 那么不同的列在上方已经被删除了.你需要注释或者删除上方程序后重新运行。

Python
# 找出 test_1 中多余的列
extra_columns = set(test_1.columns) - set(df.columns)
print(f"多余的列是: {extra_columns}")

# 如果想直接删除这些多余的列
# test_1.drop(columns=extra_columns, inplace=True)
  • 输出解释 'Unnamed: 0'
  • 这是 Pandas 读取 CSV 文件时非常常见的一个现象。

现在去观察一下你输入的两个.csv文件,train.csv的第一列是PassengerId,然而train_1.csv的第一列是一个无列名的序号列

所以读取时就会看到一个叫 Unnamed: 0 的列。 你可以在 CSV 里给这一列补上列名(比如“序号”),再重新读取一次,对比一下输出,就会更直观地理解这个现象。

它的含义是: 这通常是 CSV 文件中原本就存在的索引列(Index Column),但在保存时没有给它起名字,或者读取时没有指定它为索引。

详细解释
  1. 来源: 当你使用 df.to_csv('filename.csv') 保存文件时,默认情况下 Pandas 会把 DataFrame 的行索引(0, 1, 2, ...) 也保存进 CSV 文件,作为第一列。 因为行索引通常没有名字,所以 CSV 文件的第一行(表头)的第一个位置是空的。

  2. 读取时的表现: 当你再次用 pd.read_csv('filename.csv') 读取这个文件时,Pandas 发现第一列有数据但表头是空的,它就会自动给这一列起一个名字,叫做 Unnamed: 0(意思是:第 0 个没有名字的列)。

如何解决/避免?

你有以下几种方法处理它:

方法一:读取时指定索引(推荐) 告诉 Pandas 第一列就是索引,不要把它当成普通数据列读取。

Python
# index_col=0 表示把第0列作为行索引
test_1 = pd.read_csv('test_1.csv', index_col=0)

方法二:保存时不保存索引 如果你不需要保存行索引(通常 0,1,2... 这种默认索引是不需要保存的),在保存时加上 index=False

Python
# 保存时丢弃索引
df.to_csv('filename.csv', index=False)

方法三:读取后删除(你现在的做法) 既然已经读进来了,就把它删掉。

Python
# 把它加入到要删除的列表中
del test_1['Unnamed: 0']
# 或者
test_1.drop(columns=['Unnamed: 0'], inplace=True)

1.4.6 任务六: 将['PassengerId','Name','Age','Ticket']这几个列元素隐藏,只观察其他几个列元素

Python
df.drop(['PassengerId','Name','Age','Ticket'],axis=1).head(3)

【思考】对比任务五和任务六,是不是使用了不一样的方法(函数),如果使用一样的函数如何完成上面的不同的要求呢?

【思考回答】

如果你希望在原 DataFrame 上永久删除这些列,可以在 drop() 中使用 inplace=True

本小节只是想“暂时隐藏这几列看一眼效果”,所以不加 inplace,让原数据保持不变。

Python
# 思考回答
df.head(3) 

1.5 筛选的逻辑

对于表格数据,最重要的能力之一就是筛选:把自己需要的信息挑出来,把当前分析不关心的先丢在一边。

下面还是用实战的方式,一边写代码一边体会 pandas 的筛选能力。

1.5.1 任务一: 我们以"Age"为筛选条件,显示年龄在10岁以下的乘客信息。

Python
df[df["Age"]<10].head(3)

1.5.2 任务二: 以"Age"为条件,将年龄在10岁以上和50岁以下的乘客信息显示出来,并将这个数据命名为midage

Python
midage = df[(df["Age"]>10) &  (df["Age"]<50)]
midage.head(3)

【提示】了解pandas的条件筛选方式以及如何使用交集和并集操作

在 Pandas 中进行条件筛选(Boolean Indexing)时,逻辑运算与 Python 原生的 and/or 有所不同。

以下是核心规则和操作方式:

1. 核心逻辑符号

Pandas 使用位运算符来处理向量化的逻辑操作:

  • 交集 (AND):使用符号 &
    • 含义:同时满足所有条件。
  • 并集 (OR):使用符号 | (竖线)
    • 含义:只要满足其中一个条件即可。
  • 取反 (NOT):使用符号 ~ (波浪号)
    • 含义:排除满足该条件的数据。

2. !!!极其重要的语法规则:括号 ()

这是新手最容易报错的地方:每个独立的条件必须用括号括起来

  • 错误写法
Python
df[df['Age'] > 10 & df['Age'] < 50]
# 报错原因:Python 中 & 的优先级比 > 高,
# 它会先尝试计算 10 & df['Age'],导致逻辑混乱。
  • 正确写法
Python
df[(df['Age'] > 10) & (df['Age'] < 50)]

3. 实战示例

假设我们要处理泰坦尼克号的数据:

A. 交集 (AND) - 你的任务二

筛选年龄在 10 岁到 50 岁之间的人(大于10 小于50):

Python
# 两个条件都必须为 True
midage = df[(df["Age"] > 10) & (df["Age"] < 50)]

B. 并集 (OR)

筛选“小孩”或者“老人”(小于 10 岁 或者 大于 50 岁):

Python
# 只要满足其中一个条件就保留
extreme_age = df[(df["Age"] < 10) | (df["Age"] > 50)]

C. 列表筛选 (isin)

如果你要筛选某个字段等于多个特定值(比如筛选 1 等舱和 3 等舱),用 isin() 更简洁,它本质上也是一种并集:

Python
# 等同于 (df["Pclass"] == 1) | (df["Pclass"] == 3)
df[df["Pclass"].isin([1, 3])]

1.5.3 任务三:将midage的数据中第100行的"Pclass"和"Sex"的数据显示出来

Python
midage = midage.reset_index(drop=True)
midage.head(3)

reset_index(drop=True) 的作用是将 midage 的索引重置为默认的整数索引(0, 1, 2, ...),并且不保留原有的索引列(因为 drop=True)。

这样做可以让 midage 的索引变得连续、规范,方便后续按位置(或标签)访问或数据处理,避免因索引混乱导致的错误。

如果不加 drop=True(即写成 midage = midage.reset_index()),原有的索引会被还原为一列,变成 DataFrame 的普通数据列,而不是被直接丢弃。

具体表现为:

  • 新的 DataFrame 会多出一列名为 "index"(或原索引名),内容是原来的索引值。
  • 索引会被重置为默认的 0, 1, 2, ...。

这样做的结果是,原索引信息不会丢失,而是以新的一列保留在数据中。如果你不需要原索引,建议加 drop=True,否则会多出一列。

简要总结:
这行代码让 midage 的索引变为默认的连续数字索引,并且丢弃原来的索引,不再作为新的一列保留。

【思考】这个reset_index()函数的作用是什么?如果不用这个函数,下面的任务会出现什么情况?

reset_index() 函数是 Pandas DataFrame 的一个方法,主要作用是将索引(index)还原为普通列,并生成新的默认整数索引。常见用法有:

  • df.reset_index():将当前索引变为普通列,索引重置为默认的 0, 1, 2, ...
  • df.reset_index(drop=True):将索引重置为默认整数索引,但不保留原索引为列。

如果不用 reset_index(),在某些操作(如 groupby、set_index、过滤等)后,DataFrame 的索引可能不是默认的整数索引,而是某些列的值或多级索引。这样会导致:

  • 数据显示时索引不是连续数字,可能影响可读性。
  • 后续按位置(如 iloc)或合并、可视化等操作时,索引不规范可能导致报错或结果异常。
  • 某些情况下,索引列不会参与普通的列运算或输出,容易遗漏。

举例说明:

假设 groupby 后:

Python
df2 = df.groupby('A').sum()
print(df2)

此时 'A' 变成了索引。如果你想让 'A' 重新变成普通列,可以用:

Python
df2 = df2.reset_index()

如果不 reset_index,后续用 df2['A'] 会报错,因为 'A' 只是索引,不是普通列。

总结:reset_index() 让索引变回普通列,保证数据结构统一,便于后续处理。如果不用,可能导致数据操作和结果出现问题。

Python
# 将midage的数据中第100行的"Pclass"和"Sex"的数据显示出来
midage.loc[[100],['Pclass','Sex']]       

1.5.4 任务四:使用loc方法将midage的数据中第100,105,108行的"Pclass","Name"和"Sex"的数据显示出来

Python
midage.loc[[100,105,108],['Pclass','Name','Sex']] 

1.5.5 任务五:使用iloc方法将midage的数据中第100,105,108行的"Pclass","Name"和"Sex"的数据显示出来

Python
midage.iloc[[100,105,108],[2,3,4]]

【思考】对比ilocloc的异同

ilocloc 都是 Pandas 中用于选取 DataFrame 或 Series 数据的索引器

相同点:

  • 都可以用于行、列的选取和切片。
  • 都支持单个、多个、切片、布尔数组等多种索引方式。

不同点:

iloc(integer location) loc(label location)
索引方式 只能用整数位置(0, 1, 2, ...) 用标签(行/列名)
包含性 切片时,结尾不包含(左闭右开) 切片时,结尾包含(左闭右闭)
典型用法 df.iloc[0:3, 1:3] df.loc['a':'c', 'col1':'col3']
错误类型 超出范围时报 IndexError 标签不存在时报 KeyError

举例:

Python
# iloc 按位置
df.iloc[0:2, 1:3]  # 选第0、1行,第1、2列

# loc 按标签
df.loc['row1':'row3', 'A':'C']  # 选标签从row1到row3,列A到C(都包含结尾)

总结:

  • iloc 用于“按位置”取数据,适合数字索引。
  • loc 用于“按标签”取数据,适合行名、列名索引。

复习:在前面我们已经学习了Pandas基础,知道利用Pandas读取csv数据的增删查改,今天我们要学习的就是探索性数据分析,主要介绍如何利用Pandas进行排序、算术计算以及计算描述函数describe()的使用。

1 第一章:探索性数据分析

  • 1.6 了解你的数据吗?整体思路 在这一部分,你会从几个小任务出发,体会「探索性数据分析(EDA)」的核心思路: 通过排序、算术运算和描述性统计,逐步提出问题、验证直觉、发现数据中的模式。
  • 1.6.1 任务一:用 DataFrame 练习排序(升序) 在这一部分,你会用 np.arange()pd.DataFrame() 手动构造一个小数据表,学习:
  • 如何设置行索引和列索引;
  • 如何使用 sort_values()sort_index() 按不同维度进行排序;
  • 理解按「数据内容」排序 vs 按「行/列标签」排序的区别。
  • 1.6.2 任务二:按票价和年龄对泰坦尼克号乘客排序(降序) 在这一部分,你会对真实的泰坦尼克号数据用 sort_values(by=['票价', '年龄'], ascending=False) 做多列排序,并尝试:
  • 观察票价高的乘客在生存上的差异;
  • 练习从排序结果中总结出「业务含义」而不只是看代码输出。
  • 1.6.3 任务三:DataFrame 之间的算术计算与对齐 在这一部分,你会创建两个有重叠/不完全相同行列标签的 DataFrame,并用 frame1_a + frame1_b 等操作,理解:
  • Pandas 算术运算如何按「行列标签」自动对齐;
  • 没有对齐上的位置为什么会变成 NaN
  • 算术运算不仅是“加减乘除”,更是「自动对齐 + 缺失值处理」的一部分。
  • 1.6.4 任务四:计算泰坦尼克号上“最大家族人数” 在这一部分,你会通过 text['兄弟姐妹个数'] + text['父母子女个数'] 来构造“家族规模”这一新特征,并用 max() 找出船上最大家族的人数,体验:
  • 如何通过列之间的运算构造新特征;
  • 如何用一个简单指标(家族人数)帮助理解数据中的人物关系。
  • 1.6.5 任务五:用 describe() 获取数据的基本统计信息 在这一部分,你会对一个带缺失值的小型 DataFrame 使用 describe(),并理解:
  • count, mean, std, min, 25%, 50%, 75%, max 各统计量的含义;
  • 为什么 describe() 默认只统计数值型列;
  • 如何通过 .T 转置让结果更方便阅读。
  • 1.6.6 任务六:分析票价和“父母子女个数”的统计特征 在这一部分,你会对真实的泰坦尼克号数据使用:
  • text['票价'].describe() 分析票价分布和波动情况;
  • text['父母子女个数'].describe()(text['父母子女个数'] != 0).sum() 观察有家属同行的乘客数量;
  • info()value_counts()nunique() 等函数,快速摸清数据的列类型、缺失情况、类别分布和唯一值数量。
  • 本章小结:从函数到“分析思维” 在这一部分,你会意识到:
  • 探索性分析不仅是熟悉 API,更重要的是学会从结果中提出问题;
  • 为后续的数据清洗和建模打下「理解数据」的基础

开始之前,导入numpy、pandas包和数据

Python
# 加载所需的库
import numpy as np
import pandas as pd
Python
# 载入之前保存的train_chinese.csv数据,关于泰坦尼克号的任务,我们就使用这个数据
text = pd.read_csv('train_chinese.csv')
text.head()

1.6 了解你的数据吗?

教材《Python for Data Analysis》第五章

1.6.1 任务一:利用Pandas对示例数据进行排序,要求升序

Python
# 具体请看《利用Python进行数据分析》第五章 排序和排名 部分

#自己构建一个都为数字的DataFrame数据
frame = pd.DataFrame(np.arange(8).reshape((2, 4)), 
                     index=['2', '1'], 
                     columns=['d', 'a', 'b', 'c'])
frame

【代码解析】

pd.DataFrame() :创建一个DataFrame对象

np.arange(8).reshape((2, 4)) :

  • 生成一个 2 行 4 列的二维数组,第 1 行是 0,1,2,3,第 2 行是 4,5,6,7。
  • 并重塑为2行4列的二维数组,作为DataFrame的数据内容。

index=['2', '1'] :DataFrame 对象的行索引

columns=['d', 'a', 'b', 'c'] :DataFrame 对象的列索引

Python
# 大多数时候我们都是想根据列的值来排序,所以,将你构建的DataFrame中的数据根据某一列,升序排列
frame.sort_values(by='c', ascending=True)

可以看到sort_values这个函数中by参数指向要排列的列,ascending参数指向排序的方式(升序还是降序)

ascending=True:指定排序方式为升序(从小到大)。

注意:原始的 frame 不会被修改,除非加上 inplace=True 参数。

【总结】下面将不同的排序方式做一个小总结

Python
# 让行索引升序排序
frame.sort_index()
Python
# 让列索引降序排序
frame.sort_index(axis=1, ascending=False)
Python
# 让任选两列数据同时降序排序
frame.sort_values(by=['a', 'c'], ascending=False)

下面对这几种排序方式进行对比分析:

Python
frame.sort_values(by='c', ascending=True)           # 按列 'c' 的值升序排序
frame.sort_index()                                  # 按行索引升序排序
frame.sort_index(axis=1)                            # 按列索引升序排序
frame.sort_index(axis=1, ascending=False)           # 按列索引降序排序
frame.sort_values(by=['a', 'c'], ascending=False)   # 先按 'a',再按 'c',都降序排序

1. frame.sort_values(by='c', ascending=True)
  • 排序对象:数据内容(某一列的值)
  • 排序依据:先比较 'c' 列的值,值小的排前面
  • 常用场景:想按某一列的实际数据大小排序
2. frame.sort_index()
  • 排序对象:行索引(index)
  • 排序依据:行标签(如 '1', '2', ...)的字母或数字顺序
  • 常用场景:想让行的顺序变得有序(如从小到大)
3. frame.sort_index(axis=1)
  • 排序对象:列索引(columns)
  • 排序依据:列标签(如 'a', 'b', ...)的字母或数字顺序
  • 常用场景:让列的顺序变得有序(如从 a 到 z)
4. frame.sort_index(axis=1, ascending=False)
  • 排序对象:列索引(columns)
  • 排序依据:列标签的逆序(如 z 到 a)
  • 常用场景:想让列从大到小排列
5. frame.sort_values(by=['a', 'c'], ascending=False)
  • 排序对象:数据内容(多列的值)
  • 排序依据:先按 'a' 列降序排,如果 'a' 相同再按 'c' 列降序排
  • 常用场景:需要多重排序(比如先按成绩,再按年龄)

总结
  • sort_index按标签(行或列名)排序,适合整理表头顺序。
  • sort_values按数据内容排序,适合分析和查找极值、排名等。
  • by=['a', 'c'] 支持多列排序,优先级从左到右。
  • axis=1 控制是对行还是对列排序(0为行,1为列)。
  • ascending 控制升序还是降序。

实际应用时,按内容排序更常用于数据分析,按索引排序更常用于数据展示和整理。

1.6.2 任务二:对泰坦尼克号数据(trian.csv)按票价和年龄两列进行综合排序(降序排列),从数据中你能发现什么

Python
'''
在开始我们已经导入了train_chinese.csv数据,而且前面我们也学习了导入数据过程,根据上面学习,我们直接对目标列进行排序即可
head(20) : 读取前20条数据
'''

text.sort_values(by=['票价', '年龄'], ascending=False).head(20)

【思考】排序后,如果我们仅仅关注年龄和票价两列。根据常识我知道发现票价越高的应该客舱越好,所以我们会明显看出,票价前20的乘客中存活的有14人,这是相当高的一个比例,那么我们后面是不是可以进一步分析一下票价和存活之间的关系,年龄和存活之间的关系呢?当你开始发现数据之间的关系了,数据分析就开始了。

当然,这只是我的想法,你还可以有更多想法,欢迎写在你的学习笔记中。

1.6.3 任务三:利用Pandas进行算术计算,计算两个DataFrame数据相加结果

Python
# 具体请看《利用Python进行数据分析》第五章 算术运算与数据对齐 部分

#建立一个例子
frame1_a = pd.DataFrame(np.arange(9.).reshape(3, 3),
                     columns=['a', 'b', 'c'],
                     index=['one', 'two', 'three'])
frame1_b = pd.DataFrame(np.arange(12.).reshape(4, 3),
                     columns=['a', 'e', 'c'],
                     index=['first', 'one', 'two', 'second'])
frame1_a
Python
frame1_b
Python
#将frame_a和frame_b进行相加
frame1_a + frame1_b

【提醒】两个DataFrame相加后,会返回一个新的DataFrame,对应的行和列的值会相加,没有对应的会变成空值NaN。当然,DataFrame还有很多算术运算,如减法,除法等,有兴趣的同学可以看《利用Python进行数据分析》第五章 算术运算与数据对齐 部分,多在网络上查找相关学习资料。

1.6.4 任务四:通过泰坦尼克号数据如何计算出在船上最大的家族有多少人?

Python
'''
还是用之前导入的chinese_train.csv如果我们想看看在船上,最大的家族有多少人(‘兄弟姐妹个数’+‘父母子女个数’),我们该怎么做呢?
'''
max(text['兄弟姐妹个数'] + text['父母子女个数'])

是的,如上,很简单,我们只需找出兄弟姐妹个数和父母子女个数之和最大的数就行,先让这两列相加返回一个Series,然后用max函数求出最大值,当然你还可以想出很多方法和思考角度,欢迎你来说出你的看法。

1.6.5 任务五:学会使用Pandas describe()函数查看数据基本统计信息

Python
#(1) 关键知识点示例做一遍(简单数据)
# 具体请看《利用Python进行数据分析》第五章 汇总和计算描述统计 部分

#建立一个例子
frame2 = pd.DataFrame([[1.4, np.nan], 
                       [7.1, -4.5],
                       [np.nan, np.nan], 
                       [0.75, -1.3]
                      ], index=['a', 'b', 'c', 'd'], columns=['one', 'two'])
frame2
Python
# 调用 describe 函数,观察frame2的数据基本信息

frame2.describe()

'''
count : 该列中非空(非 NaN)数据的数量。
mean : 样本数据的平均值
std : 样本数据的标准差
min : 样本数据的最小值
25% : 样本数据25%的时候的值
50% : 样本数据50%的时候的值
75% : 样本数据75%的时候的值
max : 样本数据的最大值
'''
# frame2.describe().T
# .T 是 DataFrame 的转置(transpose)操作,会把行和列互换。

1.6.6 任务六:分别看看泰坦尼克号数据集中 票价、父母子女 这列数据的基本统计数据,你能发现什么?

Python
'''
看看泰坦尼克号数据集中 票价 这列数据的基本统计数据
'''
text['票价'].describe()

【思考】从上面数据我们可以看出, 一共有891个票价数据, 平均值约为:32.20, 标准差约为49.69,说明票价波动特别大, 25%的人的票价是低于7.91的,50%的人的票价低于14.45,75%的人的票价低于31.00, 票价最大值约为512.33,最小值为0。 当然,这只是我的想法,你还可以有更多想法,欢迎写在你的学习笔记中。

Python
'''
通过上面的例子,我们再看看泰坦尼克号数据集中 父母子女个数 这列数据的基本统计数据,然后可以说出你的想法
'''
text['父母子女个数'].describe()
Python
(text['父母子女个数'] != 0).sum()

查看数据基本信息 info():非常强大,可以一次性看到: 每一列的名称 非空值的数量(可以间接看出缺失值) 数据类型(int, float, object 等) 内存占用

Python
text['父母子女个数'].info()

统计唯一值value_counts():统计某一列中每个值出现的次数(非常适合分类数据,比如性别、登船港口)

Python
text['父母子女个数'].value_counts()

nunique():统计每一列有多少个不同的值。

Python
text.nunique()

【思考】有更多想法,欢迎写在你的学习笔记中。

【总结】本节中我们通过Pandas的一些内置函数对数据进行了初步统计查看,这个过程最重要的不是大家得掌握这些函数,而是看懂从这些函数出来的数据,构建自己的数据分析思维,这也是第一章最重要的点,希望大家学完第一章能对数据有个基本认识,了解自己在做什么,为什么这么做,后面的章节我们将开始对数据进行清洗,进一步分析。