Pandas Series数据结构
带标签的一维数组
一、概述
Pandas 是 Python 生态中最核心的数据分析库之一,而 Series 是 Pandas 的两种基本数据结构之一(另一种是 DataFrame)。可以将 Series 理解为一个带标签的一维数组——它既有传统数组(如 NumPy ndarray)的数值计算能力,又具备类似字典的键值对访问特性。
具体来说,Series 由两个紧密耦合的部分组成:
- 数据(values)——存储在底层的 NumPy 数组,可以是任意 NumPy 数据类型
- 索引(index)——一组标签,用于标识每个数据元素,类似于数据库表的主键
这种"数据 + 标签"的双重结构,使得 Series 在数据分析和处理中既保留了数组的高效计算能力,又提供了灵活的标签化数据访问方式。在深入使用之前,需要确保 Pandas 库已正确安装:
安装命令
pip install pandas numpy
然后在 Python 脚本或 Jupyter Notebook 中导入:
导入 Pandas
import pandas as pd
import numpy as np
导入后即可开始创建和操作 Series 对象。以下从最常见的创建方式开始逐步展开。
二、Series的创建
Pandas 提供了多种创建 Series 的方式,以适应不同的数据来源场景。核心构造函数是 pd.Series(data, index=index),其中 data 可以是列表(list)、字典(dict)、标量值或 NumPy 的 ndarray。下面逐一演示这四种创建方式。
2.1 从列表(list)创建
将一个 Python 列表传入 Series 构造函数,Pandas 会自动为每个元素分配默认的整数索引(RangeIndex)。
示例:从列表创建 Series
data = [10, 20, 30, 40, 50]
s = pd.Series(data)
print(s)
输出
0 10
1 20
2 30
3 40
4 50
dtype: int64
可以看到输出分为两列:左侧是索引标签(0, 1, 2, 3, 4),右侧是对应的数据值。底部显示的 dtype: int64 表示 Pandas 自动推断出了整数数据类型。
2.2 从字典(dict)创建
将 Python 字典传入时,字典的键自动成为索引标签,值成为数据。这是最自然地表达"标签-值"对应关系的方式。
示例:从字典创建 Series
population = {
"北京": 2189,
"上海": 2475,
"广州": 1868,
"深圳": 1756
}
s_pop = pd.Series(population)
print(s_pop)
输出
北京 2189
上海 2475
广州 1868
深圳 1756
dtype: int64
此时,索引标签即为城市名称,数据为对应的人口数量(单位:万人)。这种方式在数据本身具有明确的键名映射关系时非常便捷。
重要细节:索引顺序
从字典创建 Series 时,索引标签的顺序默认遵循字典的插入顺序(Python 3.7+ 中字典保持插入顺序)。如果需要自定义索引顺序,可以通过 index 参数指定。
2.3 从标量(scalar)创建
传入一个单一的标量值,必须同时指定 index 参数来确认 Series 的长度,Pandas 会将标量值重复填充到每个索引位置。
示例:从标量创建 Series
s_scalar = pd.Series(0, index=["a", "b", "c", "d", "e"])
print(s_scalar)
输出
a 0
b 0
c 0
d 0
e 0
dtype: int64
这在需要初始化一个固定长度的默认值序列时非常实用,例如为多个城市初始化相同的起始统计数据。
2.4 从 ndarray 创建
NumPy 的 ndarray 是 Series 的底层数据载体,因此直接从 ndarray 创建 Series 是最高效的方式之一。
示例:从 ndarray 创建 Series
arr = np.array([3.14, 2.718, 1.618, 0.577])
s_arr = pd.Series(arr, index=["pi", "e", "golden", "euler"])
print(s_arr)
输出
pi 3.140
e 2.718
golden 1.618
euler 0.577
dtype: float64
这里我们创建了一个包含常用数学常数的 Series。注意当数据为浮点数时,dtype 自动推断为 float64。
三、索引标签
索引标签是 Series 区别于普通一维数组的核心特性。它允许我们用有意义的名称而非数字位置来引用数据,极大地提高了代码的可读性和可维护性。
3.1 index 参数
pd.Series() 的第二个主要参数是 index,用于显式指定索引标签。索引标签可以是字符串、整数、浮点数或任何可哈希类型,且不要求标签一定唯一(尽管唯一索引更为常见和高效)。
示例:自定义索引
scores = pd.Series(
[85, 92, 78, 96],
index=["张三", "李四", "王五", "赵六"]
)
print(scores)
输出
张三 85
李四 92
王五 78
赵六 96
dtype: int64
3.2 默认 RangeIndex
当不指定 index 参数时,Pandas 会自动创建一个 RangeIndex,其值从 0 开始到 n-1(n 为数据长度)。这在不需要语义化标签的快速计算中非常实用。
RangeIndex 特性
s = pd.Series([100, 200, 300])
print("索引对象:", s.index)
print("索引类型:", type(s.index))
print("索引值列表:", s.index.tolist())
输出
索引对象: RangeIndex(start=0, stop=3, step=1)
索引类型: <class 'pandas.core.indexes.range.RangeIndex'>
索引值列表: [0, 1, 2]
RangeIndex 是一种延迟计算的索引类型,它只记录 start、stop、step 三个参数,而不实际存储所有索引值,因此在大数据量时内存开销极低。
3.3 自定义索引的实际应用
自定义索引在真实数据分析中价值巨大。例如处理时序数据时,直接将时间作为索引:
示例:时序数据的索引
dates = pd.date_range(start="2026-01-01", periods=5, freq="D")
print("生成的日期索引:")
print(dates)
print()
temperature = pd.Series(
[12.5, 13.1, 11.8, 14.2, 15.0],
index=dates
)
print("气温序列:")
print(temperature)
输出
生成的日期索引:
DatetimeIndex(['2026-01-01', '2026-01-02', '2026-01-03', '2026-01-04',
'2026-01-05'],
dtype='datetime64[ns]', freq='D')
气温序列:
2026-01-01 12.5
2026-01-02 13.1
2026-01-03 11.8
2026-01-04 14.2
2026-01-05 15.0
Freq: D, dtype: float64
将日期时间作为索引后,后续进行按时间切片、重采样、滚动计算等操作将变得十分方便。
四、核心属性
Series 对象暴露了多个实用的属性,用于快速获取数据的基本信息。掌握这些属性是高效使用 Series 的基础。
| 属性 |
类型 |
说明 |
| .values |
ndarray |
底层 NumPy 数组,存储所有数据值 |
| .index |
Index |
索引标签对象 |
| .dtype |
dtype |
数据的类型(如 int64、float64、object) |
| .name |
str 或 None |
Series 的名称,在 DataFrame 中会用作列名 |
| .shape |
tuple |
形状,一维 Series 返回 (n,) |
| .size |
int |
元素的总数量 |
| .nbytes |
int |
底层数据占用的内存字节数 |
| .empty |
bool |
Series 是否为空 |
| .hasnans |
bool |
是否包含 NaN 缺失值 |
示例:查看核心属性
s = pd.Series([10, 20, 30, 40, 50], name="scores")
print("values :", s.values)
print("index :", s.index)
print("dtype :", s.dtype)
print("name :", s.name)
print("shape :", s.shape)
print("size :", s.size)
print("nbytes :", s.nbytes)
print("empty :", s.empty)
print("hasnans :", s.hasnans)
输出
values : [10 20 30 40 50]
index : RangeIndex(start=0, stop=5, step=1)
dtype : int64
name : scores
shape : (5,)
size : 5
nbytes : 40
empty : False
hasnans : False
从中可以看到:.values 返回一个 NumPy 数组;.shape 返回的 (5,) 明确这是一维结构(只有一个维度);.nbytes 为 40 字节(5 个 int64 元素,每个 8 字节)。这些属性在数据探索和内存优化时非常有用。
五、数据访问
Series 提供了三种主要的数据访问方式:loc 标签索引、iloc 位置索引和布尔索引。选择哪种方式取决于你是根据标签、位置还是条件来获取数据。
5.1 loc —— 标签索引
.loc[] 使用索引标签来访问数据,是 Series 最独特也最强大的特性之一。语法为 s.loc[标签值],语义清晰:"给我标签等于某某的数据"。
示例:loc 标签索引
s = pd.Series([85, 92, 78, 96], index=["张三", "李四", "王五", "赵六"])
print("李四的成绩:", s.loc["李四"])
print("张三和赵六的成绩:")
print(s.loc[["张三", "赵六"]])
print("李四到赵六的成绩:")
print(s.loc["李四":"赵六"])
输出
李四的成绩: 92
张三和赵六的成绩:
张三 85
赵六 96
dtype: int64
李四到赵六的成绩:
李四 92
王五 78
赵六 96
dtype: int64
loc 切片 vs Python 切片
.loc 切片的结束位置包含在内(inclusive),这与 Python 原生的左闭右开规则不同。这是因为标签背后是语义化的名称,没有"右开"的数学意义。
5.2 iloc —— 位置索引
.iloc[] 使用整数位置(从 0 开始)来访问数据,行为与 Python 列表和 NumPy 数组的索引方式一致。
示例:iloc 位置索引
s = pd.Series([85, 92, 78, 96], index=["张三", "李四", "王五", "赵六"])
print("第一个元素:", s.iloc[0])
print("最后一个元素:", s.iloc[-1])
print("前三个元素:")
print(s.iloc[:3])
print("偶数位元素:")
print(s.iloc[::2])
输出
第一个元素: 85
最后一个元素: 96
前三个元素:
张三 85
李四 92
王五 78
dtype: int64
偶数位元素:
张三 85
王五 78
dtype: int64
loc vs iloc 核心区别
loc 通过标签访问,切片结束位置包含在内;iloc 通过整数位置访问,切片遵循左闭右开规则。当索引标签本身就是整数时,两者的行为差异尤其需要注意,切勿混淆。
5.3 布尔索引
布尔索引是按条件筛选数据的核心手段。通过将一个布尔条件表达式传给 Series 的索引操作符([]),只保留条件为 True 的行。
示例:布尔索引
s = pd.Series([85, 92, 78, 96, 88], index=["张三", "李四", "王五", "赵六", "孙七"])
high_scores = s[s >= 90]
print("成绩 >= 90:")
print(high_scores)
between = s[(s >= 80) & (s < 95)]
print("\n成绩在 80~95 之间:")
print(between)
输出
成绩 >= 90:
李四 92
赵六 96
dtype: int64
成绩在 80~95 之间:
张三 85
李四 92
dtype: int64
复合条件注意事项
在 Pandas 中使用复合条件时,必须使用 &(与)、|(或)、~(非)这三个位运算符,而不是 Python 原生的 and、or、not。并且每个条件必须用括号括起,因为位运算符的优先级高于比较运算符。
六、运算
Series 的运算机制是其区别于普通数组的又一个重要特性。当对两个 Series 进行算术运算时,Pandas 会按照索引标签自动对齐再进行计算,而不是简单地按位置配对。
6.1 对齐运算
对齐运算是指当两个 Series 进行加减乘除时,Pandas 根据索引标签的匹配关系自动对齐数据,不匹配的标签产生 NaN(缺失值)。
示例:Series 对齐运算
s1 = pd.Series([1, 2, 3], index=["a", "b", "c"])
s2 = pd.Series([4, 5, 6, 7], index=["b", "c", "d", "e"])
print("s1:")
print(s1)
print("\ns2:")
print(s2)
result = s1 + s2
print("\ns1 + s2(按标签对齐):")
print(result)
输出
s1:
a 1
b 2
c 3
dtype: int64
s2:
b 4
c 5
d 6
e 7
dtype: int64
s1 + s2(按标签对齐):
a NaN
b 6.0
c 8.0
d NaN
e NaN
dtype: float64
这里 "b" 和 "c" 在两个 Series 中都存在,因此正常相加; "a" 只在 s1 中存在,"d" 和 "e" 只在 s2 中存在,它们在对方的 Series 中没有对应的标签,所以结果为 NaN。同时,因为有 NaN 出现,结果的 dtype 从 int 自动提升为 float64。
6.2 缺失值处理
Pandas 提供了丰富的缺失值处理方法,包括检测、填充和删除:
示例:缺失值处理
s = pd.Series([1, None, 3, np.nan, 5])
print("原始数据:")
print(s)
print("\nisnull() 检测结果:")
print(s.isnull())
print("\ndropna() 删除缺失值:")
print(s.dropna())
print("\nfillna(0) 填充缺失值:")
print(s.fillna(0))
print("\nffill() 向前填充:")
print(s.ffill())
输出
原始数据:
0 1.0
1 NaN
2 3.0
3 NaN
4 5.0
dtype: float64
isnull() 检测结果:
0 False
1 True
2 False
3 True
4 False
dtype: bool
dropna() 删除缺失值:
0 1.0
2 3.0
4 5.0
dtype: float64
fillna(0) 填充缺失值:
0 1.0
1 0.0
2 3.0
3 0.0
4 5.0
dtype: float64
ffill() 向前填充:
0 1.0
1 1.0
2 3.0
3 3.0
4 5.0
dtype: float64
dropna() —— 删除缺失
直接丢弃包含 NaN 的行。适用于数据量大、缺失值占比较小的场景。操作简单但会损失数据量。
fillna() —— 填充缺失
使用指定值(0、均值、中位数等)或方法(ffill/bfill)填充。适用于数据珍贵或对长度敏感的场景。
6.3 广播运算
广播(broadcasting)是指 Series 与标量或其他序列进行算术运算时,Pandas 自动将标量应用到每个元素的过程。
示例:广播运算
prices = pd.Series({
"苹果": 6.5,
"香蕉": 3.2,
"葡萄": 12.0,
"草莓": 25.0
})
discounted = prices * 0.8
print("八折价格:")
print(discounted)
promotion = np.where(prices >= 10, prices - 2, prices)
promotion_series = pd.Series(promotion, index=prices.index)
print("\n满10减2后:")
print(promotion_series)
输出
八折价格:
苹果 5.2
香蕉 2.6
葡萄 9.6
草莓 20.0
dtype: float64
满10减2后:
苹果 6.5
香蕉 3.2
葡萄 10.0
草莓 23.0
dtype: float64
广播运算在数据预处理中极为常见,无论是单位转换(斤→公斤)、折扣计算,还是标准化、归一化等操作,都依赖该机制。
七、name属性与rename
每个 Series 对象都有一个可选的 .name 属性。这个名称在 Series 独立存在时看似无关紧要,但在 Series 被组合成 DataFrame 时,.name 会直接成为 DataFrame 的列名。
7.1 设置与查看 name
示例:name 属性
s = pd.Series([1, 2, 3], name="my_series")
print("name:", s.name)
s.name = "renamed_series"
print("修改后 name:", s.name)
s2 = pd.Series([1, 2, 3])
print("默认 name:", s2.name)
输出
name: my_series
修改后 name: renamed_series
默认 name: None
7.2 rename 方法
.rename() 方法可以原地或非原地地修改 Series 的索引标签和名称,是数据清洗中的常用工具。
示例:rename 重命名
s = pd.Series([85, 92, 78], index=["zhang", "li", "wang"], name="raw_scores")
s_renamed = s.rename(index={
"zhang": "张三",
"li": "李四",
"wang": "王五"
})
print("索引重命名后:")
print(s_renamed)
s_final = s_renamed.rename("chinese_scores")
print("\nSeries 重命名后 name:", s_final.name)
s.rename(index={"zhang": "Zhang San"}, inplace=True)
print("\n原地修改后:")
print(s)
输出
索引重命名后:
张三 85
李四 92
王五 78
Name: raw_scores, dtype: int64
Series 重命名后 name: chinese_scores
原地修改后:
Zhang San 85
li 92
wang 78
Name: raw_scores, dtype: int64
rename 不修改原数据(默认)
.rename() 默认返回一个新的 Series,原对象保持不变。只有当显式设置 inplace=True 时才会修改原对象。推荐默认使用非原地操作,保持不可变性以避免意外副作用。
八、向量化方法
Series 内置了大量向量化方法,这些方法底层的实现往往依赖于 NumPy 的高度优化 C 代码,在性能上远优于 Python 级别的循环(for-loop)。以下从统计分析、数值计算、排序三个维度进行介绍。
8.1 描述性统计
示例:常用统计方法
data = pd.Series([23, 45, 56, 78, 32, 45, 67, 89, 12, 54], name="sample_data")
print("count :", data.count())
print("mean :", data.mean())
print("std :", data.std())
print("min :", data.min())
print("max :", data.max())
print("median:", data.median())
print("skew :", data.skew())
print("kurt :", data.kurt())
print("\ndescribe() 汇总:")
print(data.describe())
输出
count : 10
mean : 50.1
std : 23.464
min : 12.0
max : 89.0
median: 49.5
skew : 0.084
kurt : -1.241
describe() 汇总:
count 10.00000
mean 50.10000
std 23.46397
min 12.00000
25% 33.50000
50% 49.50000
75% 65.25000
max 89.00000
dtype: float64
.describe() 一次性输出计数、均值、标准差、最小值、四分位数和最大值,是"EDA(探索性数据分析)第一枪"的标配工具。对于分类数据,则输出频次、唯一值数量等不同指标。
8.2 数值计算与变换
示例:数值方法
s = pd.Series([1, 2, 3, 4, 5])
print("sum :", s.sum())
print("cumsum :")
print(s.cumsum())
print("\ndiff :")
print(s.diff())
print("\npct_change :")
print(s.pct_change())
print("\nclip(2, 4) :")
print(s.clip(2, 4))
输出
sum : 15
cumsum :
0 1
1 3
2 6
3 10
4 15
dtype: int64
diff :
0 NaN
1 1.0
2 1.0
3 1.0
4 1.0
dtype: float64
pct_change :
0 NaN
1 1.000000
2 0.500000
3 0.333333
4 0.250000
dtype: float64
clip(2, 4) :
0 2
1 2
2 3
3 4
4 4
dtype: int64
8.3 排序
Series 提供按值排序和按索引排序两种方式:
示例:排序操作
s = pd.Series([88, 72, 95, 68, 83], index=["E", "B", "A", "D", "C"])
print("原始数据:")
print(s)
print("\nsort_values() 按值排序:")
print(s.sort_values())
print("\nsort_index() 按索引排序:")
print(s.sort_index())
print("\nsort_values(ascending=False) 降序:")
print(s.sort_values(ascending=False))
输出
原始数据:
E 88
B 72
A 95
D 68
C 83
dtype: int64
sort_values() 按值排序:
D 68
B 72
C 83
E 88
A 95
dtype: int64
sort_index() 按索引排序:
A 95
B 72
C 83
D 68
E 88
dtype: int64
sort_values(ascending=False) 降序:
A 95
E 88
C 83
B 72
D 68
dtype: int64
8.4 apply 与 map —— 自定义函数变换
当内置方法无法满足需求时,.apply() 和 .map() 允许将任意 Python 函数应用到 Series 的每个元素上。
示例:apply 与 map
temps_c = pd.Series([0, 10, 20, 30, 40], name="celsius")
def c_to_f(c):
return round(c * 9/5 + 32, 1)
temps_f = temps_c.apply(c_to_f).rename("fahrenheit")
grade_map = {
0: "freezing",
10: "cold",
20: "warm",
30: "hot",
40: "very hot"
}
descriptions = temps_c.map(grade_map).rename("description")
result = pd.DataFrame({
"celsius": temps_c,
"fahrenheit": temps_f,
"description": descriptions
})
print(result)
输出
celsius fahrenheit description
0 0 32.0 freezing
1 10 50.0 cold
2 20 68.0 warm
3 30 86.0 hot
4 40 104.0 very hot
apply vs map 区别
- .map() 主要用于字典映射或接受单个参数的函数,常用于分类数据转换
- .apply() 更通用,可接受任意函数,还可传递额外参数
- 两者都是元素级操作,但 .apply() 的底层实现更灵活
九、综合案例:股票收益率分析
以下通过一个模拟股票数据的完整案例,串联 Series 的多种操作。
综合案例
import pandas as pd
import numpy as np
dates = pd.date_range("2026-04-01", periods=10, freq="D")
prices = pd.Series(
[100, 102, 101, 105, 108, 107, 110, 115, 113, 118],
index=dates,
name="close_price"
)
print("=== 收盘价 ===")
print(prices)
daily_return = prices.pct_change() * 100
daily_return.name = "daily_return_pct"
print("\n=== 日收益率 (%) ===")
print(daily_return)
volatile = daily_return[abs(daily_return) >= 2]
print("\n=== 异常波动日 (收益率 >= 2%) ===")
print(volatile)
cum_return = (1 + daily_return / 100).cumprod() - 1
cum_return.name = "cum_return"
print("\n=== 累计收益率 ===")
print(cum_return)
print("\n=== 收益率统计 ===")
print(daily_return.describe())
daily_return_clean = daily_return.fillna(0)
print("\n=== 清理后的收益率 ===")
print(daily_return_clean)
输出
=== 收盘价 ===
2026-04-01 100
2026-04-02 102
2026-04-03 101
2026-04-04 105
2026-04-05 108
2026-04-06 107
2026-04-07 110
2026-04-08 115
2026-04-09 113
2026-04-10 118
Freq: D, Name: close_price, dtype: int64
=== 日收益率 (%) ===
2026-04-01 NaN
2026-04-02 2.000000
2026-04-03 -0.980392
2026-04-04 3.960396
2026-04-05 2.857143
2026-04-06 -0.925926
2026-04-07 2.803738
2026-04-08 4.545455
2026-04-09 -1.739130
2026-04-10 4.424779
Freq: D, Name: daily_return_pct, dtype: float64
=== 异常波动日 (收益率 >= 2%) ===
2026-04-02 2.000000
2026-04-04 3.960396
2026-04-05 2.857143
2026-04-07 2.803738
2026-04-08 4.545455
2026-04-10 4.424779
Name: daily_return_pct, dtype: float64
=== 累计收益率 ===
2026-04-01 0.000000
2026-04-02 0.020000
2026-04-03 0.009804
2026-04-04 0.050000
2026-04-05 0.080000
2026-04-06 0.069804
2026-04-07 0.100000
2026-04-08 0.150000
2026-04-09 0.130000
2026-04-10 0.180000
Freq: D, Name: cum_return, dtype: float64
=== 收益率统计 ===
count 9.000000
mean 1.880718
std 2.475575
min -1.739130
25% -0.953159
50% 2.000000
75% 3.361067
max 4.545455
Name: daily_return_pct, dtype: float64
=== 清理后的收益率 ===
2026-04-01 0.000000
2026-04-02 2.000000
2026-04-03 -0.980392
2026-04-04 3.960396
2026-04-05 2.857143
2026-04-06 -0.925926
2026-04-07 2.803738
2026-04-08 4.545455
2026-04-09 -1.739130
2026-04-10 4.424779
Freq: D, Name: daily_return_pct, dtype: float64
这个综合案例展示了:创建带日期索引的 Series、使用向量化方法 pct_change() 和 cumprod() 计算收益率、利用布尔索引筛选异常数据、通过 fillna() 清理缺失值、以及用 describe() 获取统计摘要——所有操作均在 Series 层面高效完成,无需编写任何显式循环。
十、核心要点总结
- Series 定义:Pandas 的带标签一维数组,由 values(数据)和 index(索引标签)两部分组成
- 四种创建方式:从列表(默认 RangeIndex)、字典(键→标签)、标量+index 参数、NumPy ndarray 创建
- 索引标签:支持字符串、整数、日期等多种类型;默认使用内存高效的 RangeIndex;自定义索引极大提升代码可读性
- 核心属性:.values(底层数组)、.index(标签对象)、.dtype(数据类型)、.name(名称,DataFrame 中用作列名)
- loc vs iloc:.loc 通过标签访问(切片两端包含),.iloc 通过整数位置访问(切片左闭右开)
- 布尔索引:通过条件表达式筛选数据,复合条件需用 &、|、~ 且每个条件加括号
- 自动对齐:算术运算按索引标签对齐,不匹配的标签产生 NaN
- 缺失值:dropna() 删除、fillna() 填充、isnull() 检测、ffill()/bfill() 前后填充
- name 与 rename:.name 标识 Series,组合为 DataFrame 时成为列名;.rename() 可重命名索引和名称
- 向量化方法:describe()、pct_change()、cumsum()、diff()、apply()、map() 等,全 C 级性能,避免 Python 显式循环
十一、进一步思考
Series 不仅是独立使用的数据结构,更是理解 DataFrame 的基石。DataFrame 本质上是由多个 Series 组成的列集合,每一列都是一个 Series。因此,掌握 Series 的所有操作——创建、索引、运算、变换——将直接迁移到 DataFrame 的使用中。建议在学习完 Series 之后,系统学习 DataFrame 的二维数据操作,包括分组聚合、透视表、多级索引等进阶内容。
练习建议
- 从 CSV 文件读取一列数据为 Series,检查其 dtype、shape、缺失值数量
- 创建一个包含 1000 个随机数的 Series,计算其移动平均线(窗口为 5)
- 将两个不同索引的 Series 相加,观察自动对齐和 NaN 的产生过程
- 使用 .apply() 实现一个自定义的文本清洗函数(去掉空格、统一大小写)
- 利用布尔索引筛选出 Series 中超出均值正负两倍标准差的数据(异常值检测)