12  极简NumPy和Pandas入门

这一章会快速介绍NumPy和Pandas。更加详细的数据清洗、处理和绘图,会在后续专门的章节进行讲解。

12.1 NumPy: 书店销售数据

NumPy用来处理多维数组,在一维的情况下,可以看成是一个超快的List。

以书店销售数据为背景,我们将依次介绍numpy的数组创建、操作、四则运算、聚合函数和布尔筛选。

12.1.1 数据准备

首先,我们从书店的基本数据开始,即书籍的价格和销售数量。

数组创建

从列表创建一维数组来记录书籍的价格和销售数量:

import numpy as np

# 书籍的单价列表
price_list = [120, 85, 100, 75, 95]
# 将列表转换为numpy数组
prices = np.array(price_list)

# 书籍的销售数量列表
quantity_list = [30, 15, 20, 10, 25]
# 将列表转换为numpy数组
quantities = np.array(quantity_list)

# 书籍名称
book_names = np.array(['微观经济学', '宏观经济学', '金融学', '会计学','Python数据分析'])


print("书籍价格数组:", prices)
print("销售数量数组:", quantities)

print("prices.shape:", prices.shape)
print("quantities.shape:", quantities.shape)
print("prices.dtype:", prices.dtype)
书籍价格数组: [120  85 100  75  95]
销售数量数组: [30 15 20 10 25]
prices.shape: (5,)
quantities.shape: (5,)
prices.dtype: int64

索引和切片

展示如何获取数组中的特定元素和子数组:本节演示如何访问数组的元素与子数组,常用语法包括 a[i]、a[i:j] 以及带步长的 a[i:j:k]。

# 获取第一本书的价格和销售数量
first_book_price = prices[0]
first_book_quantity = quantities[0]

print("第一本书的价格:", first_book_price)
print("第一本书的销售数量:", first_book_quantity)

# 获取前三本书的价格和销售数量
first_three_prices = prices[:3]
first_three_quantities = quantities[:3]

print("前三本书的价格:", first_three_prices)
print("前三本书的销售数量:", first_three_quantities)
第一本书的价格: 120
第一本书的销售数量: 30
前三本书的价格: [120  85 100]
前三本书的销售数量: [30 15 20]

布尔数组和筛选

创建布尔数组并筛选出价格高于90元的书籍:

先基于条件构造布尔掩码(如 prices > 90),再用 a[mask] 选取满足条件的元素。

# 创建一个布尔数组,用于筛选价格高于90元的书籍
high_price_filter = prices > 90
high_price_books = prices[high_price_filter]

print("价格高于90元的书籍:", high_price_books)

selected_names = book_names[high_price_filter]
print("价格>90元的书籍名称:", selected_names)
价格高于90元的书籍: [120 100  95]
价格>90元的书籍名称: ['微观经济学' '金融学' 'Python数据分析']

12.1.2 条件选择:np.where

利用 np.where 按条件进行向量化选择(条件为真取左项,否则取右项)。np.where(cond, x, y) 可按条件在两组值之间进行向量化选择:条件为真取 x,否则取 y。

labels = np.where(prices >= 100, "高价", "低价")
print("价格标签:", labels)
discounted_unit = np.where(prices >= 100, prices * 0.9, prices)
print("按条件折扣后的单价:", discounted_unit)
价格标签: ['高价' '低价' '高价' '低价' '低价']
按条件折扣后的单价: [108.  85.  90.  75.  95.]

12.1.3 数组运算

使用数组运算来计算总销售额和应用折扣。

基本数学运算

计算总销售额(未折扣前):

使用向量化运算符(+、-、*、/)完成逐元素计算,并配合 sum、mean、min、max 等聚合函数得到汇总结果。

# 计算每本书未折扣前的总销售额
total_sales = prices * quantities
print("未折扣前的总销售额:", total_sales)
print("sum(total_sales)=", total_sales.sum())
print("np.dot(prices, quantities)=", np.dot(prices, quantities))
未折扣前的总销售额: [3600 1275 2000  750 2375]
sum(total_sales)= 10000
np.dot(prices, quantities)= 10000

应用折扣

计算折后总价:

根据给定的折扣比率逐元素计算折后值,也可结合 np.where 按条件应用不同折扣。

# 书籍的折扣比率
discount_rates = np.array([0.1, 0.05, 0.15, 0.05, 0.1])  # 10%, 5%, 15%, 5%, 10%

final_prices = total_sales * (1 - discount_rates)
print("折扣后的销售额:", final_prices)
折扣后的销售额: [3240.   1211.25 1700.    712.5  2137.5 ]

12.1.4 常见函数

使用聚合和其他常见函数:展示常用汇总统计与极值定位,例如 sum、mean、min、max、argmin、argmax 等。

print("折后总销售额(合计):", final_prices.sum())
print("最低折后销售额:", final_prices.min())
print("最高折后销售额:", final_prices.max())
print("平均折后销售额:", final_prices.mean())
折后总销售额(合计): 9001.25
最低折后销售额: 712.5
最高折后销售额: 3240.0
平均折后销售额: 1800.25

12.1.5 练习题

题目 1: 价格调整

假设书店决定对所有书籍价格进行统一调整,增加5元。请使用numpy数组操作来更新prices数组,并打印新的价格数组。

题目 2: 销售筛选

书店想要了解哪些书籍的销售额在折扣后仍然超过2000元。使用布尔索引来筛选出这些书籍的价格和名称,并打印结果。

题目 3: 总结统计

请计算并打印以下统计信息:

  • 所有书籍的销售总数。
  • 折扣后每本书平均的销售额。
  • 折扣率最高的书籍的名称和价格。

题目 4: 条件选择

使用 np.where 按价格阈值(如 100 元)为每本书生成“高价/低价”标签,并构造按条件折扣后的单价向量。

例如:基于当前 prices = np.array([120, 85, ... ]),标签可得到 ["高价", "低价", ... ],按条件折扣后的单价可得到 [108.0, 85.0, ...]

12.2 Pandas: 书店数据管理

Pandas 常常用于处理二位表格数据,可以看成是一个超级Excel。

在这个例子中,我们将使用一个关于书店的数据集,包含书籍的名称、价格、销售数量和评分。通过这个例子,我们将演示如何使用pandas进行数据的创建、操作、排序和文件的读写。

12.2.1 导入pandas库

首先,导入pandas库并为其常用的别名pd

import pandas as pd

12.2.2 创建DataFrame

使用字典来创建一个DataFrame,其中包括书籍的名称、价格、销售数量和评分:

通过字典或列表的字典构造表格数据(pd.DataFrame(…)),每列为一个带索引的 Series。

data = {
    "Book": ['微观经济学', '宏观经济学', '金融学', '会计学','Python数据分析'],
    "Price": [120, 85, 100, 75, 95],
    "Quantity": [30, 15, 20, 10, 25],
    "Rating": [4.5, 4.0, 4.8, 3.9, 4.1],
}
df = pd.DataFrame(data)
df
Book Price Quantity Rating
0 微观经济学 120 30 4.5
1 宏观经济学 85 15 4.0
2 金融学 100 20 4.8
3 会计学 75 10 3.9
4 Python数据分析 95 25 4.1

12.2.3 DataFrame 结构

变量df是一个DataFrame(约等于一个二维表格),可以视为列(Series)的横向合并。

其中,每个列,又是由一个索引index和数据values组成。

再其中,数据values是来自NumPy的array,因此pandas的列也可以享受到NumPy好处,比如广播和大量函数等等。

df['Book'] # 取一列,这是一个Series
0         微观经济学
1         宏观经济学
2           金融学
3           会计学
4    Python数据分析
Name: Book, dtype: object
df['Book'].values # 取一列,然后取出取其中的数据valus, 这是一个NumPy的array
array(['微观经济学', '宏观经济学', '金融学', '会计学', 'Python数据分析'], dtype=object)
df['Book'].index # 取一列,然后取出取其中中的index(索引)
RangeIndex(start=0, stop=5, step=1)

12.2.4 数据选择与操作

12.2.4.1 使用.loc

.loc主要用于基于标签的索引,即选择行或列的名称来访问数据。

选择特定的行通过标签或位置访问与选择数据:使用 loc(标签)、iloc(位置),也可用列名列表与切片。

# 选择第一本书的数据
first_book = df.loc[0]
print("第一本书的数据:\n")
print(first_book)
第一本书的数据:

Book        微观经济学
Price         120
Quantity       30
Rating        4.5
Name: 0, dtype: object

选择特定的列

选择若干列以聚焦关键信息,例如 df[[“Book”,“Price”]] 或 df.loc[:, [“Book”,“Price”]]。

# 选择书籍名称和价格列
books_prices = df.loc[:, ["Book", "Price"]]
print("书籍和价格:\n")
books_prices
书籍和价格:
Book Price
0 微观经济学 120
1 宏观经济学 85
2 金融学 100
3 会计学 75
4 Python数据分析 95

也有快速的写法

# 选择书籍名称和价格列
books_prices = df[["Book", "Price"]]
print("书籍和价格:\n")
books_prices
书籍和价格:
Book Price
0 微观经济学 120
1 宏观经济学 85
2 金融学 100
3 会计学 75
4 Python数据分析 95

12.2.4.2 使用.iloc

.iloc用于基于位置的索引,即选择行或列的数值索引来访问数据。

选择特定的行

iloc 按整数位置选择行列,可使用切片或整数列表获取多行多列。

# 选择前三本书的数据
first_three_books = df.iloc[:3]
print("前三本书的数据:\n")
first_three_books
前三本书的数据:
Book Price Quantity Rating
0 微观经济学 120 30 4.5
1 宏观经济学 85 15 4.0
2 金融学 100 20 4.8

选择特定的列选择若干列以聚焦关键信息,例如 df[[“Book”,“Price”]] 或 df.loc[:, [“Book”,“Price”]]。

# 选择价格和数量列(索引1和2)
price_quantity = df.iloc[:, [1, 2]]
print("价格和数量:\n")
price_quantity
价格和数量:
Price Quantity
0 120 30
1 85 15
2 100 20
3 75 10
4 95 25

12.2.5 条件筛选

使用条件表达式来筛选满足特定条件的行。

根据条件选择行构造布尔表达式筛选行(如 df[“Rating”] > 4.0),多条件使用 & 或 | 并配合括号。

# 选择评分高于4.0的书籍
high_rated_books = df[df["Rating"] > 4.0]
print("评分高于4.0的书籍:\n") 
high_rated_books
评分高于4.0的书籍:
Book Price Quantity Rating
0 微观经济学 120 30 4.5
2 金融学 100 20 4.8
4 Python数据分析 95 25 4.1

使用query方法 这是一个更动态的方式来选择数据,允许使用字符串表达式来指定条件。query 以字符串表达式筛选,例如 df.query(“Price < 100”),变量可用 @var 引用。

# 使用query方法选择价格小于100的书籍
affordable_books = df.query("Price < 100")
print("价格小于100的书籍:\n")
affordable_books
价格小于100的书籍:
Book Price Quantity Rating
1 宏观经济学 85 15 4.0
3 会计学 75 10 3.9
4 Python数据分析 95 25 4.1

12.2.6 修改数据列

添加一列“Total Sales”,计算每本书的总销售额:

直接用向量化表达式生成新列,例如 df[“Total Sales”] = df[“Price”] * df[“Quantity”]。

df["Total Sales"] = df["Price"] * df["Quantity"]
df
Book Price Quantity Rating Total Sales
0 微观经济学 120 30 4.5 3600
1 宏观经济学 85 15 4.0 1275
2 金融学 100 20 4.8 2000
3 会计学 75 10 3.9 750
4 Python数据分析 95 25 4.1 2375

12.2.7 数据排序

根据总销售额对书籍进行降序排序:使用 sort_values 按指标排序,支持多列与升降序控制。

df_sorted = df.sort_values(by="Total Sales", ascending=False)
print("按总销售额排序:\n")
df_sorted
按总销售额排序:
Book Price Quantity Rating Total Sales
0 微观经济学 120 30 4.5 3600
4 Python数据分析 95 25 4.1 2375
2 金融学 100 20 4.8 2000
1 宏观经济学 85 15 4.0 1275
3 会计学 75 10 3.9 750

12.2.8 缺失值处理

演示缺失值检测与填充:先构造缺失,再使用 isna 计数与 fillna 填充。先检测缺失(isna / isnull),再按需求填充(fillna)或丢弃(dropna)。

df_na = df.copy()
df_na.loc[1, "Rating"] = None # 添加一个缺失值
df_na
Book Price Quantity Rating Total Sales
0 微观经济学 120 30 4.5 3600
1 宏观经济学 85 15 NaN 1275
2 金融学 100 20 4.8 2000
3 会计学 75 10 3.9 750
4 Python数据分析 95 25 4.1 2375
df_na["Rating"].isna() # 问,Rating这一列,谁是缺失值?
0    False
1     True
2    False
3    False
4    False
Name: Rating, dtype: bool
# 上一个单元格中的boo序列是可以求和的:True被视为1,False被视为0,等于问,有多少个True
print("Rating 缺失值个数:", df_na["Rating"].isna().sum()) 
df_na["Rating"] = df_na["Rating"].fillna(df_na["Rating"].mean())
df_na
Rating 缺失值个数: 1
Book Price Quantity Rating Total Sales
0 微观经济学 120 30 4.500 3600
1 宏观经济学 85 15 4.325 1275
2 金融学 100 20 4.800 2000
3 会计学 75 10 3.900 750
4 Python数据分析 95 25 4.100 2375

12.2.9 数据合并

concat 用于按行或按列堆叠数据(理解为把2个数据横向或者纵向粘贴在一起,对齐index);merge 用于按键连接表(以某一列(或者多列)为依据,合并数据)。

# 构造一个新的行:一本新书
df_new = pd.DataFrame({
    "Book": ["计量经济学"],
    "Price": [110],
    "Quantity": [12],
    "Rating": [4.2],
})
df_new
Book Price Quantity Rating
0 计量经济学 110 12 4.2
# concat接受一个list,其中是你要合并的2个DataFrame或者Series
# 默认是纵向合并(添加行,或者说,添加样本/观测值)
df_concat = pd.concat([df, df_new], ignore_index=True)
df_concat
Book Price Quantity Rating Total Sales
0 微观经济学 120 30 4.5 3600.0
1 宏观经济学 85 15 4.0 1275.0
2 金融学 100 20 4.8 2000.0
3 会计学 75 10 3.9 750.0
4 Python数据分析 95 25 4.1 2375.0
5 计量经济学 110 12 4.2 NaN
# 构造一个新的数据:出版社
df_info = pd.DataFrame({
    "Book": ["微观经济学", "金融学", "大学语文"],
    "Publisher": ["A社", "B社", "C社"],
})
df_info
Book Publisher
0 微观经济学 A社
1 金融学 B社
2 大学语文 C社
# 把新数据merge到原数据中
# on: 按什么列来合并,可以取多列,后面要用到
# how:哪个表为依据:left 即完全保留左侧的表(df),把左侧表中有的样本,找到右侧表对应的数据合并进来,左表没有的样本就不管。
df_merge = df.merge(df_info, on="Book", how="left")
df_merge
Book Price Quantity Rating Total Sales Publisher
0 微观经济学 120 30 4.5 3600 A社
1 宏观经济学 85 15 4.0 1275 NaN
2 金融学 100 20 4.8 2000 B社
3 会计学 75 10 3.9 750 NaN
4 Python数据分析 95 25 4.1 2375 NaN

12.2.10 读取和写入数据

写入CSV文件

将DataFrame写入CSV文件,文件名为bookstore.csv:将数据保存为 CSV(to_csv)并用 read_csv 读回以便后续分析。

df.to_csv("bookstore.csv", index=False)
print("数据已写入 'bookstore.csv'")
数据已写入 'bookstore.csv'

读取CSV文件 读取之前写入的CSV文件,查看内容:

df_loaded = pd.read_csv("bookstore.csv")
print("从CSV文件加载的数据:\n")
df_loaded
从CSV文件加载的数据:
Book Price Quantity Rating Total Sales
0 微观经济学 120 30 4.5 3600
1 宏观经济学 85 15 4.0 1275
2 金融学 100 20 4.8 2000
3 会计学 75 10 3.9 750
4 Python数据分析 95 25 4.1 2375

12.2.11 练习题

题目 1: 计算平均评分

假设你想了解书店中所有书籍的平均评分。使用pandas的函数计算并打印这个数据集中所有书籍的平均评分。

题目 2: 筛选特定的书籍

书店老板对销售表现不佳的书籍(总销售额低于2000元)进行特价处理。请筛选出这些书籍的名称和当前价格,并打印结果。

题目 3: 新书上架 书店有新书上架,需要更新DataFrame。请将下面的新书信息添加到现有的DataFrame中,并重新打印更新后的DataFrame。

新书信息:

  • Book: ‘计量经济学’
  • Price: 90
  • Quantity: 20
  • Rating: 4.3

题目 4: 添加出版社信息

给定下方字典 publisher_info,请将其并入现有 DataFrame df,使结果包含新列 Publisher。请自行选择合适的连接方式与参数。

publisher_info = {
    "Book": ["微观经济学", "宏观经济学", "金融学", "会计学", "Python数据分析", "计量经济学"],
    "Publisher": ["A社", "B社", "B社", "C社", "D社", "E社"],
}