[学习笔记] 1. 机器学习前置知识

  1. 视频链接
  2. 所有数据集下载地址:数据集 1. 机器学习前置知识

目录

  • 1. 机器学习概述
    • 1.1 机器学习算法分类
      • 1.1.1 监督学习
      • 1.1.2 无监督学习
      • 1.1.3 半监督学习
      • 1.1.4 强化学习
    • 1.2 模型评估
      • 1.2.1 分类模型评估
      • 1.2.2 回归模型评估
    • 1.3 拟合
      • 1.3.1 欠拟合
      • 1.3.2 过拟合
  • 2. Matplotlib
    • 2.1 示例
    • 2.2 常见的注意事项
      • 2.2.1 坐标轴
      • 2.2.2 显示中文字体
      • 2.2.3 图片保存
    • 2.3 在一个坐标系中绘制多个图像
    • 2.4 多个坐标系显示 —— plt.subplots(面向对象的画图方法)
    • 2.5 折线图的应用场景
    • 2.6 常见图形绘制
      • 2.6.1 绘制散点图
      • 2.6.2 绘制柱状图
  • 3. Numpy
    • 3.1 ndarray的介绍
      • 3.1.1 ndarray与Python原生的list运算效率对比
      • 3.1.2 ndarray的优势
    • 3.2 ndarray的属性、形状、类型
      • 3.2.1 ndarray的属性
      • 3.2.2 ndarray的形状
      • 3.2.3 ndarray的类型
    • 3.3 ndarray的基本操作
      • 3.3.1 [生成数组]生成0和1的数组
      • 3.3.2 [生成数组]从现有数组生成
      • 3.3.3 [生成数组]生成固定范围的数组
      • 3.3.4 [生成数组]生成随机数组
        • 场景一:正态分布
        • 场景二:均匀分布
      • 3.3.5 数组的索引、切片
      • 3.3.6 形状修改
        • 第一种:ndarray.reshape(shape, order)
        • 第二种:ndarray.resize(new_shape)
        • 第三种:ndarray.T
      • 3.3.7 类型修改
        • 第一种:ndarray.astype(type)
        • 第二种:ndarray.tostring([order])或ndarray.tobytes([order])
      • 3.3.8 数组的去重
    • 3.4 ndarray的运算
      • 3.4.1 逻辑运算
      • 3.4.2 通用判断函数
      • 3.4.3 np.where(三元运算符)
    • 3.5 统计运算
      • 3.5.1 统计指标及其API
      • 3.5.2 案例:学生成绩统计
    • 3.6 数组间的运算
      • 3.6.1 数组与数的运算
      • 3.6.2 数组与数组的运算
      • 3.6.3 广播机制
    • 3.7 矩阵
    • 3.8 向量
    • 3.9 矩阵的加法和标量乘法
    • 3.10 矩阵向量的乘法
    • 3.11 矩阵乘法的性质
    • 3.12 矩阵的逆和转置
    • 3.13 矩阵乘法的日常应用举例
    • 3.14 Numpy中的矩阵乘法
    • 3.15 [扩展]shape=(3, ), shape=(3, 1), shape=(1, 3)的区别
  • 4. Pandas
    • 4.1 Series数据结构
      • 4.1.1 Series的创建
      • 4.1.2 Series的属性
    • 4.2 Dataframe数据结构
      • 4.2.1 DataFrame的创建
      • 4.2.2 DataFrame的属性
      • 4.2.3 DataFrame索引的设置
    • 4.3 MultiIndex
      • 4.3.1 MultiIndex的特性
      • 4.3.2 MultiIndex的创建
    • 4.4 索引操作
      • 4.4.1 直接使用行列索引(先列后行)
      • 4.4.2 结合loc或者iloc使用索引
    • 4.5 赋值操作
    • 4.6 排序
      • 4.6.1 DataFrame排序
      • 4.6.2 Series排序
    • 4.7 DataFrame运算
      • 4.7.1 算术运算
      • 4.7.2 逻辑运算
      • 4.7.3 统计运算
      • 4.7.4 自定义运算
    • 4.8 Pandas画图
    • 4.9 文件读取与存储
      • 4.9.1 CSV
      • 4.9.2 HDF5
      • 4.9.3 JSON
    • 4.10 高级处理:缺失值处理
    • 4.11 高级处理:数据离散化
    • 4.12 高级处理:合并
    • 4.13 高级处理:交叉表与透视表
    • 4.14 高级处理:分组与聚合
    • 4.15 案例:综合应用
      • 4.15.1 需求
      • 4.15.2 实现

1. 机器学习概述

1.1 机器学习算法分类

根据数据集组成不同,可以把机器学习算法分为:

  1. 监督学习
  2. 无监督学习
  3. 半监督学习
  4. 强化学习

1.1.1 监督学习

定义:输入数据是由输入特征值和目标值组成。

  • 函数的输出可以是一个连续的值(称为回归)
  • 输出是有限个离散值(称作分类)

监督学习可分为:

  1. 回归问题:预测房价,根据样本集拟合出一条连续曲线。
  2. 分类问题:根据肿瘤特征判断良性还是恶性,得到的是结果是“良性”或者“恶性”,是离散的。

1.1.2 无监督学习

定义:输入数据是由输入特征值组成,没有目标值。

  • 输入数据没有被标记,也没有确定的结果。样本数据类别未知,需要根据样本间的相似性对样本集进行类别划分。

有监督、无监督算法对比:

对比
监督学习
无监督学习
输入的数据有特征值,有目标值
目标值连续即为回归
目标值离散即为分类
输入的数据有特征值,无目标值

1.1.3 半监督学习

定义:训练集同时包有标记样本数据和未标记样本数据。

1.1.4 强化学习

定义:实质是make decisions问题,即自动进行决策,并且可以做连续决策。

举例:小孩想要走路,但在这之前,他需要先站起来,站起来之后还要保持平衡。接下来还要先迈出一条腿,是左腿还是右腿,迈出一步后还要迈出下一步。
小孩就是agent,他试图通过采取行动(即行走)来操纵环境(行走的表面),并且从一个状态转变到另一个状态(即他走的每一步)。当他完成任务的子任务(即走了几步)时,孩子得到奖励(给巧克力吃);并且当他不能走路时,就不会给巧克力。

主要包含五个元素: agent, action, reward, environment, observation;

强化学习的目标就是获得最多的累计奖励

监督学习和强化学习的对比:

监督学习 强化学习
反馈映射 输出的是之的关系,可以告诉算法什么样的输入对应着什么样的输出 输出的是给机器的反馈reward function,即用来判断这个行为是好是坏
反馈时间 做了比较坏的选择会立刻反馈给算法 结果反馈有延时,有时候可能需要走了很多步以后才知道以前的某一步的选择是好还是坏
输入特征 输入是独立同分布的 面对的输入总是在变化,每当算法做出一个行为,它影响下一次决策的输入

拓展概念:什么是独立同分布。

独立同分布IID(Independent and Identically Distributed)

在概率统计理论中,如果变量序列或者其他随机变量有相同的概率分布,并且互相独立,那么这些随机变量是独立同分布。

在西瓜书中解释是:输入空间中的所有样本服从一个隐含未知的分布,训练数据所有样本都是独立地从这个分布上采样而得。

简单解释——独立、同分布、独立同分布:

  1. 独立:每次抽祥之间没有关系。不会相互影响。
    举例:给一个骰子,每次抛骰子抛到几就是几,这是独立;如果我要抛骰子两次之和大于8,那么第一次和第二次抛就不独立,因为第二次抛的结果和第一次相关。
  2. 同分布:每次抽样,样本服从同一个分布
    举例:给一个骰子,每次抛骰子得到任意点数的概率都是六分之一,这个就是同分布
  3. 独立同分布:每次抽样之间独立而且同分布

小结

In Out 目的 案例
监督学习 有标签 有反馈 预测结果 猫狗分类、房价预测
无监督学习 无标签 无反馈 发现潜在结构 “物以聚类,人以群分”
半监督学习 部分有标签,部分无标签 有反馈 降低数据标记的难度
强化学习 决策流程及激励系统 一系列行动 长期利益最大化 学下棋

1.2 模型评估

学习目标:

  • 了解机器学习中模型评估的方法。
  • 知道过拟合、欠拟合发生情况

模型评估是模型开发过程不可或缺的一部分。它有助于发现表达数据的最佳模型和所选模型将来工作的性能如何。

按照数据集的目标值不同,可以把模型评估分为分类模型评估和回归模型评估。

1.2.1 分类模型评估

  1. 准确率(Accuracy):预测正确的数占样本总数的比例。
  2. 其他评价指标:精确率(Precision)、召回率(Recall)、F1-score、AUC指标等

1.2.2 回归模型评估

均方根误差(Root Mean Squared Error,RMSE):RMSE是一个衡量回归模型误差率的常用公式。不过,它仅能比较误差是相同单位的模型。

R M S E = ∑ i n ( p i − y ^ i ) 2 n RMSE = \sqrt{\frac{\sum_i^n(p_i - \hat{y}_i)^2}{n}} RMSE=nin(piy^i)2

其中:

  • y y y 为预测值
  • y ^ \hat{y} y^ 为真实值

其他评价指标:

  • 相对平方误差(Relative Squared Error,RSE)
  • 平均绝对误差(Mean AbsoluteError,MAE)
  • 相对绝对误差(Relative Absolute Error,RAE)

1.3 拟合

模型评估用于评价训练好的的模型的表现效果,其表现效果大致可以分为过拟合和欠拟合。

在训练过程中,你可能会遇到如下问题:

训练数据训练的很好啊,误差也不大,为什么在测试集上面有问题呢?当算法在某个数据集当中出现这种情况,可能就出现了拟合问题。

1.3.1 欠拟合

[学习笔记] 1. 机器学习前置知识_第1张图片

因为机器学习到的天鹞特征太少了,导致区分标准太粗糙,不能准确识别出天鹅。

欠拟合(under-fitting):模型学习的太过粗糙,连训练集中的样本数据特征关系都没有学出来。

1.3.2 过拟合

[学习笔记] 1. 机器学习前置知识_第2张图片

机器已经基本能区别天鹅和其他动物了。然后,很不巧已有的天鹅图片全是白天鹅的,于是机器经过学习后,会认为天鹅的羽毛都是白的,以后看到羽毛是黑的天鹅就会认为那不是天鹅。

过拟合(over-fitting):所建的机器学习模型或者是深度学习模型在训练样本中表现得过于优越,导致在测试数据集中表现不佳。


小结:

  • 分类模型评估【了解】
    • 准确率
  • 回归模型评估【了解】
    • RMSE——均方根误差
  • 拟合【知道】
    • 欠拟合
      • 学习到的东西太少
      • 模型学习的太过粗糙
    • 过拟合
      • 学习到的东西太多
      • 学习到的特征多,不好泛化

2. Matplotlib

Matplotlib是一个用于绘制数据可视化图表的Python库。它可以用来创建各种类型的图表,例如线图、散点图、柱状图、直方图、饼图等。Matplotlib具有广泛的功能,可以满足各种绘图需求,并且可以与其他Python库(例如NumPy和Pandas)结合使用。它是数据科学和机器学习领域中最受欢迎的可视化工具之一。

示例:

import matplotlib.pyplot as plt
import random


# 1. 创建画布
plt.figure(figsize=(20, 8), dpi=100)  # figsize=(长, 宽)

# 2. 绘制图像
x = [i for i in range(1, 10)]
y = [i for i in range(11, 20)]
random.shuffle(y)
plt.plot(x, y)

# 3. 图像显示
plt.show()

[学习笔记] 1. 机器学习前置知识_第3张图片

2.1 示例

为了更好地理解所有基础绘图功能,我们通过天气温度变化的绘图来融合所有的基础API使用需求:画出某城市11点到12点1小时内每分钟的温度变化折线图,温度范围在15度~18度。

import matplotlib.pyplot as plt
import random
from pylab import mpl
# 设置中文字体
mpl.rcParams["font.sans-serif"] = ["SimHei"]
# 设置正常显示符号
mpl.rcParams["axes.unicode_minus"] = False


# 0. 准备数据
x = range(60)
y = [random.uniform(15, 18) for i in x]

# 1. 创建画布
plt.figure(figsize=(20, 8), dpi=100)

# 2. 绘制图像
plt.plot(x, y)

## 2.1 自定义x,y轴刻度
x_tickes_label = [f"11点{i}分" for i in x]
y_tickes_label = range(40)

## 2.2 指定xy轴的空隙
plt.xticks(x[::5], x_tickes_label[::5], fontsize=12)  #  xticks(ticks=None, labels=None, **kwargs)
plt.yticks(y_tickes_label[::5], fontsize=12)

# 2.3 添加网格显示
plt.grid(visible=True, linestyle="--", alpha=0.5)  # alpha为透明度

# 2.4 添加描述信息
plt.xlabel("时间", fontsize=14)
plt.ylabel("温度", fontsize=14)
plt.title("中午11点~12点某城市温度变化图", fontsize=20)

# 2.5 保存图片(保存图片一定要在plt.show()之前,否则会保存一张空的图片)
plt.savefig("./test.png")

# 3. 图像显示
plt.show()

2.2 常见的注意事项

2.2.1 坐标轴

xticks(ticks=None, labels=None, **kwargs)

  • ticks:x轴刻度位置的列表,若传入空列表,即不显示x轴
  • labels:放在指定刻度位置的标签文本。当ticks参数有输入值,该参数才能传入参数

2.2.2 显示中文字体

from pylab import mpl
# 设置中文字体
mpl.rcParams["font.sans-serif"] = ["SimHei"]
# 设置正常显示符号
mpl.rcParams["axes.unicode_minus"] = False

2.2.3 图片保存

保存图片一定要在plt.show()之前,否则会保存一张空的图片。

2.3 在一个坐标系中绘制多个图像

需求:再添加一个城市的温度变化。

收集到北京当天温度变化情况,温度在1度到3度。效果如下:

[学习笔记] 1. 机器学习前置知识_第4张图片

import matplotlib.pyplot as plt
import random
from pylab import mpl
# 设置中文字体
mpl.rcParams["font.sans-serif"] = ["SimHei"]
# 设置正常显示符号
mpl.rcParams["axes.unicode_minus"] = False


# 0. 准备数据
x = range(60)
y = [random.uniform(15, 18) for i in x]
y_BJ = [random.uniform(1, 3) for i in x]

# 1. 创建画布
plt.figure(figsize=(20, 8), dpi=100)

# 2. 绘制图像
plt.plot(x, y, label="上海温度")
plt.plot(x, y_BJ, color="red", linestyle="--", label="北京温度")

## 2.1 自定义x,y轴刻度
x_tickes_label = [f"11点{i}分" for i in x]
y_tickes_label = range(40)

## 2.2 指定xy轴的空隙
plt.xticks(x[::5], x_tickes_label[::5], fontsize=12)  #  xticks(ticks=None, labels=None, **kwargs)
plt.yticks(y_tickes_label[::5], fontsize=12)

# 2.3 添加网格显示
plt.grid(visible=True, linestyle="--", alpha=0.5)  # alpha为透明度

# 2.4 添加描述信息
plt.xlabel("时间", fontsize=14)
plt.ylabel("温度", fontsize=14)
plt.title("中午11点~12点某城市温度变化图", fontsize=20)

# 2.5 保存图片(保存图片一定要在plt.show()之前,否则会保存一张空的图片)
plt.savefig("./test.png")

# 2.6 显示图例
plt.legend(loc="best", fontsize=18)

# 3. 图像显示
plt.show()

2.4 多个坐标系显示 —— plt.subplots(面向对象的画图方法)

如果我们想要将上海和北京的天气图显示在同一个图的不同坐标系当中。效果如下:

[学习笔记] 1. 机器学习前置知识_第5张图片

可以通过subplots函数实现(旧的版本中有subplot,使用起来不方便),推荐subplots函数

matplotlib.pyplot.subplots(nrows=1, ncols=1, **fig_kw)创建一个带有多个axes(坐标系/绘图区)的图。

Parameters:

nrows, ncols: 设置几行几列坐标系
  int, optional, default: 1, Number of rows/columns of the subplot grid.

Return:
  fig: 图对象
  axes: 返回相应数量的坐标系

设置标题等方法不同:
  set_xticks
  set_yticks
  set_xlabel
  set_ylabel

关于axes子坐标系的更多方法:参考https://matplotlib.org/stable/api/axes_api.html

注意:plt.函数名()相当于面向过程的画图方法,axes.set_方法名()相当于面向对象的画图方法

import matplotlib.pyplot as plt
import random
from pylab import mpl
# 设置中文字体
mpl.rcParams["font.sans-serif"] = ["SimHei"]
# 设置正常显示符号
mpl.rcParams["axes.unicode_minus"] = False


# 0. 准备数据
x = range(60)
y = [random.uniform(15, 18) for _ in x]
y_BJ = [random.uniform(1, 3) for _ in x]


# 1. 创建画布
# plt.figure(figsize=(20, 8), dpi=100)
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(20, 8), dpi=100)


# 2. 绘制图像
# plt.plot(x, y, label="上海温度")
# plt.plot(x, y_BJ, color="red", linestyle="--", label="北京温度")
axes[0].plot(x, y, label="上海温度")
axes[1].plot(x, y_BJ, color="red", linestyle="--", label="北京温度")

## 2.1 自定义x,y轴刻度
x_tickes_label = [f"11点{i}分" for i in x]
y_tickes_label = range(40)

## 2.2 指定xy轴的空隙
# plt.xticks(x[::5], x_tickes_label[::5], fontsize=12)  #  xticks(ticks=None, labels=None, **kwargs)
# plt.yticks(y_tickes_label[::5], fontsize=12)
axes[0].set_xticks(x[::5])
axes[0].set_yticks(y_tickes_label[::5])
axes[0].set_xticklabels(x_tickes_label[::5])

axes[1].set_xticks(x[::5])
axes[1].set_yticks(y_tickes_label[::5])
axes[1].set_xticklabels(x_tickes_label[::5])

# 2.3 添加网格显示
# plt.grid(visible=True, linestyle="--", alpha=0.5)  # alpha为透明度
axes[0].grid(visible=True, linestyle="--", alpha=0.5)
axes[1].grid(visible=True, linestyle="--", alpha=0.5)

# # 2.4 添加描述信息
# plt.xlabel("时间", fontsize=14)
# plt.ylabel("温度", fontsize=14)
# plt.title("中午11点~12点某城市温度变化图", fontsize=20)
axes[0].set_xlabel("时间", fontsize=14)
axes[0].set_ylabel("温度", fontsize=14)
axes[0].set_title("中午11点~12点上海市温度变化图", fontsize=20)

axes[1].set_xlabel("时间", fontsize=14)
axes[1].set_ylabel("温度", fontsize=14)
axes[1].set_title("中午11点~12点北京市温度变化图", fontsize=20)

# 2.5 保存图片(保存图片一定要在plt.show()之前,否则会保存一张空的图片)
plt.savefig("./test.png")

# 2.6 显示图例
# plt.legend(loc="best", fontsize=18)
axes[0].legend(loc="best", fontsize=18)
axes[1].legend(loc="best", fontsize=18)


# 3. 图像显示
plt.show()

2.5 折线图的应用场景

举例:

  • 呈现公司产品(不同区域)每天活跃用户数
  • 呈现app每天下载数量
  • 呈现产品新功能上线后,用户点击次数随时间的变化
  • 拓展:画各种数学函数图像

注意:plt.plot()除了可以画折线图,也可以用于画各种数学函数图像。

[学习笔记] 1. 机器学习前置知识_第6张图片


小结:

  • 添加x,y轴列度【知道】
    • plt.xticks()
    • plt.yticks()
    • 注意:在传递进去的第一个参数必须是数字,不能是字符串。如果是字符串,需要进行替换操作
  • 添加网格显示【知道】
    • plt.grid(linestyle="--"", alpha=0.5)
  • 添加描述信息【知道】
    • plt.xlabel()
    • plt.ylabel()
    • plt.title()
  • 图像保存【知道】
    • plt.savefig("路径")
  • 多次plot【了解】
    • 直接进行添加就OK
  • 显示图例【知道】
    • plt.legend(loc="best")
    • 注意:一定要在plt.plot()里面设置一个label。如果不设置,没法显示
  • 多个坐标系显示【了解】
    • plt.subplots(nrows=, ncols=)
  • 折线图的应用【知道】
    • 应用于观察数据的变化
    • 画出一些数学函数图像

2.6 常见图形绘制

学习目标:掌握常见统计图及其意义。

Matplotlib能够绘制折线图、散点图、柱状图、直方图、饼图。我们需要知道不同的统计图的意义,以此来决定选择哪种统计图来呈现我们的数据。

[学习笔记] 1. 机器学习前置知识_第7张图片

  1. 折线图:以折线的上升或下降来表示统计数量的增减变化的统计图。
    1. 特点:能够显示数据的变化趋势,反映事物的变化情况。——变化
    2. api:plt.plot(x, y)
  2. 散点图:用两组数据构成多个坐标点,考察坐标点的分布,判断两变量之间是否存在某种关联或总结坐标点的分布模式。
    1. 特点:判断变量之间是否存在数量关系趋势,展示离群点。——分布规律
    2. api:plt.scatter(x, y)
  3. 柱状图:排列在工作表的列或行中的数据可以绘制到柱状图中。
    1. 特点:绘制离散的数据,能够一眼看出各个数据的大小,比较数据之间的差别。——统计/对比
    2. api:plt.bar(x, width, align="center", **kwargs)
    3. 参数说明:
      1. x:需要传递的数据
      2. width:柱状图的宽度
      3. align:每个柱状图的位置对齐方式:{"center", "edge"}, optional, default: "center"
      4. **kwargs
        1. color:选择柱状图的颜色
  4. 直方图:由一系列高度不等的纵向条纹或线段表示数据分布的情况。一般用横轴表示数据范围,纵轴表示分布情况。
    1. 特点:绘制连续性的数据,展示一组或多组数据的分布情况。——统计
    2. api:plt.hist(x, bins=None)
    3. 参数说明:
      1. x:需要传递的数据
      2. bins:bins参数指定直方图的箱子数量,用于将数据分成若干个区间并计算每个区间内数据的频数。可以通过调整bins的值来控制直方图的分辨率和精度,从而更好地理解数据的分布情况。如果不指定bins参数,则默认将数据分为10个区间。
  5. 饼图:用于表示不同分类的占比情况,通过弧度大小来对比各种分类,
    1. 特点:分类数据的占比情况。——占比
    2. api:plt.pie(x, labels=, autopct=, colors)
    3. 参数说明:
      1. x:数量,自动算百分比
      2. labels:每个section的名称
      3. autopct:占比显示指定%1.2f%
      4. colors:每部分颜色

直方图和柱状图的区别:

直方图和柱状图都是用于表示数据分布的图形,但它们的意义和使用场景不同。

直方图是一种连续型数据的图形表示方法,横轴表示数据的取值范围,纵轴表示数据出现的频率或概率密度。直方图的连续条形图之间没有间隔,因为它们代表的是连续的数据范围,因此在直方图中所有的条形都是相邻且相互重叠的,而且它们的宽度通常是不等的。直方图通常用于表示数据的分布情况,包括数据的集中程度、对称性、偏斜程度等。

柱状图则是一种离散型数据的图形表示方法,横轴表示不同的类别或数据,纵轴表示数量或比例。柱状图的条形之间有间隔,因为它们代表的是不同的类别或数据,它们的宽度通常是相等的。柱状图通常用于比较不同类别或数据之间的数量或比例。

因此,直方图和柱状图的区别在于数据类型和表示方式。直方图适用于连续型数据的分布表示,而柱状图适用于离散型数据的比较表示

直方图的横坐标表示数据的取值范围,可以理解为数据的区间或者分组,每个区间内包含的数据范围是一样的。而直方图的纵坐标表示每个区间内数据出现的频数或频率,也可以表示为概率密度。因此,直方图的纵轴高度越高,表示这个区间内的数据出现的频率越高,反之亦然。通过直方图,我们可以直观地了解数据的分布情况和数据的集中程度。

2.6.1 绘制散点图

需求:退散房屋面积和房屋价格的关系。

房屋面积数据:

x = [225.98, 247.07, 253.14, 457.85, 241.58, 301.01, 20.67, 288.64,
    163.56, 120.06, 207.83, 342.75, 147.9, 53.06, 224.72, 29.51, 
    21.61, 483.21, 245.25, 399.25, 343.35]

房屋价格数据:

y = [196.63, 203.88, 210.75, 372.74, 202.41, 247.61, 24.9, 239.34, 
    140.32, 104.15, 176.84, 288.23, 128.79, 49.64, 191.74, 33.1, 
    30.74, 400.02, 205.35, 330.64, 283.45]
import matplotlib.pyplot as plt
from pylab import mpl
# 设置中文字体
mpl.rcParams["font.sans-serif"] = ["SimHei"]
# 设置正常显示符号
mpl.rcParams["axes.unicode_minus"] = False


x = [225.98, 247.07, 253.14, 457.85, 241.58, 301.01, 20.67, 288.64,
    163.56, 120.06, 207.83, 342.75, 147.9, 53.06, 224.72, 29.51, 
    21.61, 483.21, 245.25, 399.25, 343.35]

y = [196.63, 203.88, 210.75, 372.74, 202.41, 247.61, 24.9, 239.34, 
    140.32, 104.15, 176.84, 288.23, 128.79, 49.64, 191.74, 33.1, 
    30.74, 400.02, 205.35, 330.64, 283.45]

# 1. 创建画布
plt.figure(figsize=(20, 8), dpi=100)

# 2. 绘制图像
plt.scatter(x, y)

# 2.1 添加细节
plt.title("房屋面积与房屋价格散点图", fontsize=20)
plt.xlabel("房屋面积(元)", fontsize=14)
plt.ylabel("房屋价格(元)", fontsize=14)

# 3. 显示图像
plt.show()

效果如下:

[学习笔记] 1. 机器学习前置知识_第8张图片

2.6.2 绘制柱状图

需求:对比每部电影的票房收入。

import matplotlib.pyplot as plt
from pylab import mpl
# 设置中文字体
mpl.rcParams["font.sans-serif"] = ["SimHei"]
# 设置正常显示符号
mpl.rcParams["axes.unicode_minus"] = False
import random


# 电影名称
movie_name = ["电影" + str(i) for i in range(1, 11)]

x = range(len(movie_name))
y = [random.randint(10000, 20000) for _ in range(10)]

# 1. 创建画布
plt.figure()

# 2. 绘制图像
movie_color_seed = ['b', 'r', 'g', 'y', 'c', 'm', 'k', 'b']
movie_color = [movie_color_seed[random.randint(0, len(movie_color_seed)-1)] for _ in range(len(movie_name))]
plt.bar(x, y, width=0.5, color=movie_color)

# 2.1 添加细节
plt.title("不同电影单日票房收入", fontsize=20)
plt.xlabel("电影名", fontsize=14)
plt.ylabel("单日票房(元)", fontsize=14)

# 2.2 修改x轴显示
plt.xticks(x, movie_name)

# 2.3 添加网格
plt.grid(linestyle="--", alpha=0.5)

# 3. 显示图像
plt.show()

[学习笔记] 1. 机器学习前置知识_第9张图片


小结:

  • 折线图【知道】
    • 能够显示数据的变化趋势,反映事物的变化情况。(变化)
    • plt.plot()
  • 散点图【知道】
    • 判断变量之间是否存在数量关联趋势,展示离群点(分布规律)
    • plt.scatter()
  • 柱状图【知道】
    • 绘制连离散的数据,能够一眼看出各个数据的大小,比较数据之间的差别。(统计/对比)
    • plt.bar(x, width, align="center")
  • 直方图【知道】
    • 绘制连续性的数据展示一组或者多组数据的分布状况(统计)
    • plt.hist(x, bins)
  • 饼图【知道】
    • 用于表示不同分类的占比情况,通过弧度大小来对比各种分类(占比)
    • plt.pie(x, labels, autopct, colors)

3. Numpy

NumPy是Python中的一个开源数学计算库,它提供了一个高性能的多维数组对象,以及用于处理这些数组的工具。它是科学计算、数据分析和机器学习领域中常用的库之一,广泛应用于各种领域,如自然语言处理、图像处理、信号处理、统计分析等。NumPy是基于C语言编写的,因此它的计算速度非常快,远远超过了Python原生的列表和数组的计算速度。

Numpy使用ndarray对象来处理多维数组,该对象是一个快速而灵活的大数据容器。

学习目标:

  • 了解Numpy运算速度上的优势
  • 知道数组的属性,形状、类型
  • 应用Numpy实现数组的基本操作
  • 应用随机数组的创建实现正态分布应用
  • 应用Numpy实现数组的逻辑运算
  • 应用Numpy实现数组的统计运算
  • 应用Numpy实现数组之间的运算

3.1 ndarray的介绍

NumPy提供了一个N维数组类型ndarray,它描述了相同类型的"items"的集合。

ndarray是NumPy中的一个核心对象,它是一个多维数组对象,可以存储任意类型的数据(但确定了数据类型后,ndarray容器中所有元素的数据类型必须相同),并且支持各种数学运算。ndarray的全称是n-dimensional array,即N维数组,它可以是1维数组、2维数组、3维数组等任意维度的数组。ndarray中的每个元素在内存中是连续存储的,因此它的计算速度非常快。

ndarray的创建非常灵活,可以通过多种方式创建,如从Python列表、元组等数据结构转换而来,也可以通过各种函数直接创建,例如zerosonesarangelinspace等函数。

ndarray是NumPy中最常用的对象之一,几乎所有的NumPy函数都是基于ndarray进行计算的,因此熟练掌握ndarray的使用对于学习和使用NumPy非常重要。

语文 数学 英语 政治 体育
87 55 87 64 63
61 83 93 76 99
89 78 100 63 90
84 71 47 100 73
40 73 94 53 71
99 63 71 41 41
57 52 72 61 66

可以使用ndarray进行存储:

import numpy as np


score = np.array(
  [[ 87  55  87  64  63]
  [ 61  83  93  76  99]
  [ 89  78 100  63  90]
  [ 84  71  47 100  73]
  [ 40  73  94  53  71]
  [ 99  63  71  41  41]
  [ 57  52  72  61  66]]
)

print(score)

提问:使用Python列表list可以存储一维数组,通过列表的嵌套可以实现多维数组,那么为什么还需要使用numpy的ndarray呢?

3.1.1 ndarray与Python原生的list运算效率对比

在这里我们通过一段代码运行来体会到ndarray的好处。

import numpy as np
import time
import random


a = []
for i in range(100000000):
    a.append(random.random())
    
# 通过%time魔法方法查看当前代码运行一行所花费的时间
%time sum1 = sum(a)

b = np.array(a)
%time sum2 = np.sum(b)


"""
    CPU times: total: 250 ms
    Wall time: 429 ms
    CPU times: total: 31.2 ms
    Wall time: 106 ms
"""

从中我们看到ndarray的计算速度要快很多,节约了时间。

机器学习的最大特点就是大量的数据运算,那么如果没有一个快速的解决方案,那可能现在Python也在机器学习领域达不到好的效果。

Numpy专门针对ndarray的操作和运算进行了设计,所以数组的存储效率和输入输出性能远优于Python中的嵌套列表,数组越大,Numpy的优势就越明显。

思考:ndarray为什么可以这么快?

3.1.2 ndarray的优势

  1. 内存块风格
  2. ndarray支持并行化运算(向量化运算)
  3. 效率远高于纯Python代码

一、内存块风格

从图中我们可以看出ndarray在存储数据的时候,数据与数据的地址都是连续的,这样就给使得批量操作数组元素时速度更快。

这是因为ndarray中的所有元素的类型都是相同的,而Python列表中的元素类型是任意的,所以ndarray在存储元素时内存可以连续,而Python原生list就只能通过寻址方式找到下一个元素,这虽然也导致了在通用性能方面Numpy的ndarray不及Python原生list,但在科学计算中,Numpy的ndarray就可以省掉很多循环语句,代码使用方面比Python原生list简单的多。

[学习笔记] 1. 机器学习前置知识_第10张图片

ndarray存储元素的数据类型必须相同。这是由于ndarray中的所有元素在内存中是连续存储的,而不同类型的数据在内存中所占用的空间大小不同,因此如果存储不同类型的数据,就无法保证在内存中的连续性和一致性。

在创建ndarray时,可以通过指定dtype参数来指定数据类型,如果不指定,则默认为float64类型。例如,可以通过以下方式创建一个存储整数类型的一维数组:

import numpy as np

arr = np.array([1, 2, 3], dtype=int)

在这个例子中,通过dtype=int指定了数组的数据类型为整数类型。如果不指定dtype,则默认为float64类型。

需要注意的是,如果在创建ndarray时指定的数据类型与数组中的元素类型不匹配,则会自动进行类型转换。例如,如果将一个整数类型的数组赋值给一个浮点类型的数组,则会自动将整数类型转换为浮点类型。但是,在进行类型转换时需要注意数据精度的问题,避免数据精度的损失。

ndarray是支持str这类数据类型的,一般不常用而已。

二、ndarray支持并行化运算(向量化运算)

Numpy内置了并行运算功能,当系统有多个核心时,在做某种计算时,Numpy会自动做并行计算。

三、ndarray支持并行化运算(向量化运算)

Numpy底层使用C语言编写,内部解除了GIL(全局解释器锁),其对数组的操作速度不受Python解释器的限制,所以,其效率远高于纯Python代码。


小结:

  • numpy介绍【了解】
    • 一个开源的Python科学计算库
    • 计算起来要比Python简洁高效
    • Numpy使用ndarray对象来处理多维数组
  • ndarray介绍【了解】
    • NumPy提供了一个N维数组类型ndarray,它描述了相同类型的item的集合。
    • 生成numpy对象: np.array()
  • ndarray的优势【掌握】
    • 内存块风格
      • list – 分离式存储,存储内容多样化
      • ndarray – 一体式存储,存储类型必须一样
    • ndarray支持并行化运算(向量化运算)
    • ndarray底层是用C语言写的,效率更高,不受GIL的影响

3.2 ndarray的属性、形状、类型

学习目标:了解数组的属性、形状、类型。

3.2.1 ndarray的属性

数组属性反映了数组本身固有的信息。

属性名字 返回值
ndarray.shape 数组维度的元组
ndarray.ndim 数组维数
ndarray.size 数组中的元素数量(容器的大小)
ndarray.itemsize 一个数组元素的长度(字节)
ndarray.dtype 数组元素的类型

因为是类的属性,所以不需要加()调用!

3.2.2 ndarray的形状

a = np.array([[1, 2, 3], [4, 5, 6]])
b = np.array([1, 2, 3, 4])
c = np.array([[[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]]])

print(a.shape)  # (2, 3)
print(b.shape)  # (4,)
print(c.shape)  # (2, 2, 3)

print(a.ndim, "维")  # 2维
print(b.ndim, "维")  # 1维
print(c.ndim, "维")  # 3维

3.2.3 ndarray的类型

type(score.dtype)  # numpy.dtype[int32]

dtype是numpy.dtype类型,先看看对于数组来说都有哪些类型:

名称 描述 简写
np.bool 用一个字节存储的布尔类型(True或False) 'b'
np.int8 一个字节大小,-128 ~ 127 ( − 2 7 → 2 7 − 1 -2^7 \to 2^7-1 27271) 'i'
np.int16 整数, − 2 15 -2^{15} 215(-32768) ~ 2 15 − 1 2^{15}-1 2151(32767) 'i2'
np.int32 整数, − 2 31 -2^{31} 231 ~ 2 31 − 1 2^{31}-1 2311 'i4'
np.int64 整数, − 2 63 -2^{63} 263 ~ 2 63 − 1 2^{63}-1 2631 'i8'
np.uint8 无符号整数,0 ~ 255( 2 8 − 1 2^{8} - 1 281) 'u'
np.uint16 无符号整数,0 ~ 65535( 2 16 − 1 2^{16}-1 2161) 'u2'
np.uint32 无符号整数,0 ~ 2 32 − 1 2^{32}-1 2321 'u4'
np.uint64 无符号整数,0 ~ 2 64 − 1 2^{64}-1 2641 'u8'
np.float16 半精度浮点数:16位,正负号1位,指数5位,精度10位 f2
np.float32 单精度浮点数:32位,正负号1位,指数8位,精度23位 f4
np.float64 双精度浮点数:64位,正负号1位,指数11位,精度52位 f8
np.complex64 复数,分别用两个32位浮点数表示实部和虚部 'c8'
np.complex128 复数,分别用两个64位浮点数表示实部和虚部 'c16'
np.object_ Python对象 'O'
np.string_ 字符串 'S'
np.unicode_ Unicode类型 'U'

创建数组的时候指定类型,若不指定,整数默认int64,小数默认为float64。

a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32)
print(a.dtype)  # float32

# 类型转换
b = a.astype(np.int64)  # 不改变原ndarray的dtype,需要新的ndarray数组接收
print(a.dtype)  # float32
print(b.dtype)  # int64

a.dtype = np.float16
print(a.dtype)  # float16

# ndarray存储字符串
c = np.array([["Python", "Hello"], ["Hello", "World"]])
print(c.dtype)  # 

3.3 ndarray的基本操作

学习目标:

  • 理解数组的各种生成方法
  • 应用数组的索引机制实现数组的切片获取
  • 应用维度变换实现数组的形状改变
  • 应用类型变换实现数组类型改变
  • 应用数组的转换

3.3.1 [生成数组]生成0和1的数组

  1. np.ones(shape, dtype)
  2. np.ones_like(a, dtype)
  3. np.zeros(shape, dtype)
  4. np.zeros_like(a, dtype)

其中:

  • shape为形状的tuple或list
  • a为一个ndarray数组

例子:

"""
生成0和1的数组
    1. np.ones(shape, dtype)
    2. np.ones_like(a, dtype)
    3. np.zeros(shape, dtype)
    4. np.zeros_like(a, dtype)
    
其中:
    shape为形状的tuple或list
    a为一个ndarray数组
"""


# 1. np.ones(shape, dtype)
a = np.ones([4, 8], dtype=np.int64)
a = np.ones((4, 8), dtype=np.int64)  # 两种方法都可以
print(a)
"""
[[1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1]]
"""


# 2. np.ones_like(a, dtype)
b = np.ones_like(a, dtype=np.float64)
print(b)
"""
[[1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1.]]
"""


## 3. np.zeros(shape, dtype)
c = np.zeros([3, 3])
print(c)
print(c.dtype)  # float64
"""
[[0. 0. 0.]
 [0. 0. 0.]
 [0. 0. 0.]]
"""

## 4. np.zeros_like(a, dtype)
d = np.zeros_like(a, dtype=np.int64)
print(d)
"""
[[0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0]]
"""

3.3.2 [生成数组]从现有数组生成

从现有数组生成新数组的方式有两种方式:

  1. np.array(object, dtype):深拷贝
  2. np.asarray(a, dtype):浅拷贝

示例:

a = np.array([[1, 2, 3], [4, 5, 6]])

# 从现有的数组当中创建
a1 = np.array(a)
print(a1)
"""
[[1 2 3]
 [4 5 6]]
"""

# 相当于索引的形式,并没有真正的创建一个新的数组
a2 = np.asarray(a)
print(a2)
"""
[[1 2 3]
 [4 5 6]]
"""

两种从现有数组生成新数组方式的不同之处:

  • np.array(arr)是深拷贝
  • np.asarray(arr)是浅拷贝

示例:

a = np.array([[1, 2, 3], [4, 5, 6]])
print(a)
"""
[[1 2 3]
 [4 5 6]]
"""

# 深拷贝
a1 = np.array(a)  
print(a1)
"""
[[1 2 3]
 [4 5 6]]
"""

# 浅拷贝
a2 = np.asarray(a)  
print(a2)
"""
[[1 2 3]
 [4 5 6]]
"""

# 修改值
a[0, 0] = 100
print(a)
"""
[[100   2   3]
 [  4   5   6]]
"""

print(a1)
"""
[[1 2 3]
 [4 5 6]]
"""

print(a2)
"""
[[100   2   3]
 [  4   5   6]]
"""

3.3.3 [生成数组]生成固定范围的数组

有三种方式:

  1. np.linspace(start, stop, num, endpoint)
    • 作用:创建等差数组一指定数量
    • 参数:
      • start:序列的起始值
      • stop:序列的终止值
      • num:要生成的等间隔样例数量,默认为50
      • endpoint:序列中是否包含stop值,默认为True
  2. np.arange(start, stop, step, dtype)
    • 作用:创建等差数组并指定步长
    • 参数:
      • start:序列的起始值
      • stop:序列的终止值
      • step:步长,默认值为1
  3. np.logspace(start, stop, num)
    • 作用:创建等比数列(默认以10为底,修改参数base即可)
    • 参数:
      • num:要生成的等比数列数量,默认为50

举例:

# 1. np.linspace(start, stop, num, endpoint)
arr = np.linspace(0, 100, 11)
print(arr)  # [  0.  10.  20.  30.  40.  50.  60.  70.  80.  90. 100.]
print(arr.dtype)  # float64
print(arr.shape)  # (11,)
print(arr.ndim)  # 1

arr = np.linspace(0, 100, 11, dtype=np.int64)
print(arr)  # [  0  10  20  30  40  50  60  70  80  90 100]


# 2. np.arange(start, stop, step, dtype)
arr = np.arange(0, 10, 2)
print(arr)  # [0 2 4 6 8]
print(arr.dtype)  # int32


# 3. np.logspace(start, stop, num)
arr = np.logspace(0, 2, 3)
print(arr)  # [  1.  10. 100.]
print(arr.dtype)  # float64

3.3.4 [生成数组]生成随机数组

核心:np.random模块。

场景一:正态分布

一、基础概念复习:正态分布(理解)

a. 什么是正态分布?

正态分布是一种概率分布。正态分布是具有两个参数 μ \mu μ σ \sigma σ的连续型随机变量的分布,第一参数 μ \mu μ是服从正态分布的随机变量的均值,第二个参数 σ \sigma σ是此随机变量的方差,所以正态分布记作 N ( μ , σ ) N(\mu, \sigma) N(μ,σ)

[学习笔记] 1. 机器学习前置知识_第11张图片


b. 正态分布的应用?

生活、生产与科学实验中很多随机变量的概率分布都可以近似地用正态分布来描述。


c. 正态分布特点

  • μ \mu μ决定了其位置,其标准差 σ \sigma σ决定了分布的幅度( σ \sigma σ越大,越矮胖, σ \sigma σ越小,越高瘦)。
  • μ = 0 , σ = 1 \mu=0, \sigma=1 μ=0,σ=1时的正态分布是标准正态分布。

d. μ \mu μ σ \sigma σ的求解:

  • 均值: μ = x 1 + x 2 + . . . + x n n \mu = \frac{x_1+ x_2 + ... + x_n}{n} μ=nx1+x2+...+xn
  • 方差: σ 2 = ( x 1 − μ ) 2 + ( x 2 − μ ) 2 + . . . + ( x n − μ ) 2 n \sigma^2 = \frac{(x_1-\mu)^2 + (x_2 - \mu)^2 + ... + (x_n - \mu)^2}{n} σ2=n(x1μ)2+(x2μ)2+...+(xnμ)2
  • 标准差: σ = 1 n ∑ i = 1 n ( x i − μ ) 2 \sigma = \sqrt{\frac{1}{n}\sum_{i=1}^n(x_i-\mu)^2} σ=n1i=1n(xiμ)2

其中 n n n为数据总个数。


e. 标准差与方差的意义:可以理解为为数据的离散程度:

  • 当标准差越大时,数据越分散
  • 当标准差越小时,数据越紧密

[学习笔记] 1. 机器学习前置知识_第12张图片


二、正态分布创建方式

  1. np.random.randn(d0, d1, ..., dn)
    • 功能:从标准正态分布中返回一个或多个样本值
    • 参数:d0, d1, ..., dn为返回值的shape
  2. np.random.normal(loc=0.0, scale=1.0, size=None)
    • 功能:用于生成符合正态分布(高斯分布)的随机数
    • 参数:
      • loc: float:此概率分布的均值(对应着整个分布的中心centre)
      • scale: float:此概率分布的标准差(对应于分布的宽度,scale越大越矮胖;scale越小,越瘦高)
      • size: int or tuple of ints:输出的shape,默认为None,只输出一个值
  3. np.random.standard_normal(size=None)
    • 功能:返回指定形状标准正态分布的数组。

举例1:生成均值为1.75,标准差为1的正态分布数据,100000000个

x1 = np.random.normal(loc=1.75, scale=1, size=100000000)
print(x1)  # [2.9201503  2.00146211 1.42538312 ... 2.37253866 3.15273716 1.4312834 ]
print(x1.shape)  # (100000000,)

画出该图像:

import numpy as np
import matplotlib.pyplot as plt
from pylab import mpl
# 设置中文字体
mpl.rcParams["font.sans-serif"] = ["SimHei"]
# 设置正常显示符号
mpl.rcParams["axes.unicode_minus"] = False


x1 = np.random.normal(loc=1.75, scale=1, size=100000000)

plt.figure()
plt.hist(x1, 1000)  # plt.hist(数据,组距)

plt.xlabel("数值")
plt.ylabel("出现的次数")
plt.title("np.random.normal(loc=1.75, scale=1, size=100000000)")

plt.show()

[学习笔记] 1. 机器学习前置知识_第13张图片


举例2:随机生成4支股票1周的交易日涨幅数据。

随机生成涨跌幅在某个正态分布内,比如均值0,方差1:

# 创建符合正态分布的4只股票5天的涨跌幅数据
stock_change = np.random.normal(loc=0, scale=1, size=[4, 5])
print(stock_change)

返回结果:

[[ 1.74073454  0.13384577 -1.15700707  0.4169004  -0.30539835]
 [-0.62736583  0.01223323 -0.00524497  0.61583305 -0.98697203]
 [ 0.59925948  0.45820375 -1.86175106 -0.59848094 -0.29423742]
 [-2.83592608 -0.90993324  0.99710497 -0.65825899  0.934991  ]]

场景二:均匀分布

  1. np.random.rand(d0, d1, ..., dn)
    • 功能:返回[0.0, 1.0)区间的一组均匀分布的数。
    • 参数:d0, d1, ..., dn为返回值的shape
  2. np.random.uniform(low=0.0, high=1.0, size=None)
    • 功能:从一个均匀分布[low,high)区间中随机采样,注意定义域是左闭右开,即包含low,不包含high
    • 参数介绍:
      • low:采样下界,float类型,默认值为0;
      • high:采样上界,float类型,默认值为1;
      • size:输出样本数目,为int或元组(tuple)类型,例如,size=(m, n, k),则输出mnk个样本,缺省时输出1个值。
    • 返回值:ndarray类型,其形状和参数size中描述一致。
  3. np.random.randint(low, high=None, size=None, dtype=)
    • 功能:从一个均匀分布中随机采样,生成一个整数或N维整数数组
    • 取数范围:若high不为None时,取[low,high)区间的随机整数,否则取值[0,low)区间的随机整数。

示例:

# 1. np.random.rand(d0, d1, ..., dn)
arr1 = np.random.rand(2, 3)
print(arr1)
"""
[[0.03633364 0.35502351 0.18559704]
 [0.72854027 0.80302261 0.82313366]]
"""


# 2. np.random.uniform(low=0.0, high=1.0, size=None)
arr2 = np.random.uniform(low=1, high=10, size=[2, 3])
print(arr2)
"""
[[2.04443524 4.44514576 4.41786813]
 [7.25102016 5.02834325 9.75242862]]
"""


# 3. np.random.randint(low, high=None, size=None, dtype=)
arr3 = np.random.randint(0, 10, size=[2, 3])
print(arr3)
"""
[[6 2 6]
 [1 7 8]]
"""

画图:

import numpy as np
import matplotlib.pyplot as plt
from pylab import mpl
# 设置中文字体
mpl.rcParams["font.sans-serif"] = ["SimHei"]
# 设置正常显示符号
mpl.rcParams["axes.unicode_minus"] = False


x2 = np.random.uniform(-1, 1, 10000000)

plt.figure()
plt.hist(x2, 1000)

plt.xlabel("数值")
plt.ylabel("出现的次数")
plt.title("np.random.uniform(low=0.0, high=1.0, size=None)")

plt.show()

[学习笔记] 1. 机器学习前置知识_第14张图片

3.3.5 数组的索引、切片

一维、二维、三维的数组如何索引?

  • 直接进行索引,切片
  • 对象[:, :]:先行后列

示例:

# 二维数组
arr_2d = np.random.normal(loc=0, scale=1, size=[4, 5])
print(arr_2d, "\r\n")
"""
[[ 0.66610887  0.60331317  0.75724806  0.65241078 -1.09456943]
 [-0.70859007  0.45414909  0.00162145 -0.60455284 -1.43747196]
 [ 0.36120038  0.51901282 -0.60272547  1.18274746  1.14338992]
 [ 0.08029827 -0.65563637  1.50467988 -0.25218452  1.03742514]]
"""

print(arr_2d[0, 0:3], "\r\n")
"""
[0.66610887 0.60331317 0.75724806]
"""


# 三维数组
arr_3d = np.random.randint(0, 100, size=[2, 3, 4])
print(arr_3d, "\r\n")
"""
[[[53 74 59 48]
  [35 19 73 92]
  [25 92 95 86]]

 [[56  8 66 17]
  [62 95 94  4]
  [31 32 92 19]]] 
"""
print(arr_3d[-1, :, 1:])
"""
[[ 8 66 17]
 [95 94  4]
 [32 92 19]]
"""

3.3.6 形状修改

一般有三种修改ndarray形状的接口:

  1. ndarray.reshape(shape, order)
  2. ndarray.resize(new_shape)
  3. ndarray.T

第一种:ndarray.reshape(shape, order)

  • 功能:返回一个具有相同数据域,但shape不一样的视图
  • 参数:
    • shape:新的形状
    • order:顺序
    • 有返回值(深拷贝)
  • 注意:
    • reshape函数会返回一个新的数组,而不会改变原始数组的形状。如果需要改变原始数组的形状,可以使用ndarray.resize(new_shape)函数
    • 可以使用-1来自动推导形状

示例:

arr = np.random.random(size=[4, 5])
print(arr.shape)  # (4, 5)

# 1. ndarray.reshape(shape, order)
arr.reshape([5, 4])
print(arr.shape)  # (4, 5)
arr.reshape([2, -1])
print(arr.shape)  # (4, 5)

# reshape函数会返回一个新的数组,而不会改变原始数组的形状
new_arr = arr.reshape([5, 4])
print(new_arr.shape)  # (5, 4)

# 可以使用-1来自动推导形状
new_arr = arr.reshape([10, -1])
print(new_arr.shape)  # (10, 2)

需要注意的是,如果原数组是一个视图(view),则reshape操作也会返回一个视图,而不是创建新的数组对象。视图是共享数据存储的,因此,对视图的修改会影响原数组的数据。可以使用numpy.base属性来判断数组是否是视图,如果是视图,则numpy.base属性返回它所共享数据存储的原始数组对象,如果不是,则返回None。

以下是一个示例代码:

import numpy as np


# 创建一个数组
a = np.array([1, 2, 3, 4, 5, 6])

# 创建一个视图
b = a[0:4]

# reshape操作返回一个新的数组对象
c = a.reshape(2, 3)

print(a)  # [1 2 3 4 5 6]
print(b)  # [1 2 3 4]
print(c)  # [[1 2 3]
          #  [4 5 6]]

# 修改新数组不会影响原数组,但会影响视图
c[0, 0] = 0
print(a)  # [0 2 3 4 5 6]
print(c)  # [[0 2 3]
          #  [4 5 6]]

# 视图的reshape操作返回一个视图
d = b.reshape(2, 2)
print(d)  # [[0 2]
          #  [3 4]]
print(d.base)  # [0 2 3 4 5 6]

第二种:ndarray.resize(new_shape)

  • 功能:修改数组本身的形状(需要保持元素个数前后相同)
  • 没有返回值(如果直接使用=则是浅拷贝!)
  • 注意:
    • darray.resize(new_shape)函数会改变原始数组的形状并返回None
    • 不可以使用-1来自动推导形状

示例:

arr = np.random.random(size=[4, 5])
print(arr.shape)  # (4, 5)

# 2. ndarray.resize(new_shape)
arr.resize([5, 4])
print(arr.shape)  # (5, 4)
arr.resize([2, 10])
print(arr.shape)  # (2, 10)

# 不可以使用-1来自动推导形状
arr.resize([-1, 10])
print(arr.shape)  # ValueError: negative dimensions not allowed

第三种:ndarray.T

  • 数组的转置:将数组的行、列行互换
  • 返回转置后的数组(浅拷贝,相当于是视图view)

arr.T返回一个转置后的ndarray对象,这个操作是浅拷贝。它不会创建新的数组对象,而是返回原数组的一个视图,只是视图的维度顺序发生了变化。

如果需要创建一个新的数组对象,可以使用np.transpose方法,它会返回一个转置后的新数组对象,不会影响原数组。

示例:

arr = np.random.random(size=[4, 5])
print(arr.shape)  # (4, 5)

# 3. ndarray.T
new_arr = arr.T
print(arr.shape)  # (4, 5)
print(new_arr.shape)  # (5, 4)

3.3.7 类型修改

ndarray类型的修改一般有两种方法:

  1. ndarray.astype(new_type)
  2. ndarray.tostring([order]) / ndarray.tobytes([order])

第一种:ndarray.astype(type)

  • 作用:返回修改类型之后的数组
  • 注意:ndarray.astype(type) 返回一个新的 ndarray 数组,其中的数据类型被转换成了指定的 type。这个操作是浅拷贝。

具体来说,新数组的数据类型发生了变化,但是数据本身并没有被复制,只是新数组和原数组共享同一内存区域中的数据。因此,如果修改新数组中的数据,原数组中对应的数据也会被修改。

实测是深拷贝而非浅拷贝

示例:

arr = np.array([[1, 2, 3], [4, 5, 6]])
print(arr)
"""
[[1 2 3]
 [4 5 6]]
"""

new_arr = arr.astype(np.float32)
print(new_arr)
"""
[[1. 2. 3.]
 [4. 5. 6.]]
"""

第二种:ndarray.tostring([order])或ndarray.tobytes([order])

  • 作用:构造包含数组中原始数据字节的Python字节。
  • 用途:主要用途是将数组中的原始数据转换为 Python 字节对象。这样可以将数组中的数据以二进制形式存储或传输。例如,可以使用此方法将数组数据写入文件或通过网络发送。

ndarray.tostring([order])方法快被弃用了,建议使用ndarray.tobytes([order])方法。

示例:

arr = np.array([[1, 2, 3], [4, 5, 6]])
arr_string = arr.tobytes()
print(arr_string)
"""
"\nb'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00'\n"
"""

3.3.8 数组的去重

  • np.unique(arr, return_index=False, return_inverse=False, return_counts=False,)
    • 作用:返回去重后的数组,对原数组无影响。
    • 参数:
      • arr:传入的数组
      • return_index=True:返回ndarray索引,该索引为新数组(去重后的数组)元素在原数组中的位置
      • return_inverse=True:返回ndarray索引,该索引为原数组元素在新数组(去重后的数组)中的位置
      • return_counts=True:返回ndarray索引,该索引为新数组(去重后的数组)元素在原数组中出现的次数
    • 返回值:去重后的ndarray数组 + [idx](看return_index, return_inverse, return_counts是否为True

np.unique 函数用于找出数组中独一无二的元素值,并按照从小到大排序。它的参数包括 arreturn_indexreturn_inversereturn_countsar 是输入数组,除非设定了 axis 参数,否则输入数组均会被自动扁平化成一个一维数组。return_index 是一个可选参数,如果为 True 则结果会同时返回被提取元素在原始数组中的索引值。return_inverse 是一个可选参数,如果为 True 则结果会同时返回原始数组中的元素在新数组中的索引值。return_counts 是一个可选参数,如果为 True 则结果会同时返回去重数组中的元素在原数组中的出现次数。

np.unique()函数支持多维数组(ndarray)

示例:

arr = np.array([1, 2, 3, 4, 1, 1, 2])
print(arr)  # [1 2 3 4 1 1 2]

# 默认返回去重后的ndarray
unique_arr = np.unique(arr)
print(unique_arr)  # [1 2 3 4]


# return_index=True:返回去重后的ndarray及其元素在原数组(arr)中的索引
unique_arr, idx = np.unique(arr, return_index=True)
print(unique_arr)  # [1 2 3 4]
print(idx)  # [0 1 2 3]


# return_inverse=True:返回去重后的ndarray和arr中的元素在unique_arr的位置
unique_arr, idx = np.unique(arr, return_inverse=True)
print(unique_arr)  # [1 2 3 4]
print(idx)  # [0 1 2 3 0 0 1]


# return_counts=True:返回去重后的ndarray及其元素在arr中出现的次数
unique_arr, idx = np.unique(arr, return_counts=True)
print(unique_arr)  # [1 2 3 4]
print(idx)  # [3 2 1 1]

小结:

  • 创建数组【掌握】
    • 生成0和1的数组
      • np.ones()
      • np.ones_like()
    • 从现有数组中生成
      • np.array() – 深拷贝
      • np.asarray – 浅拷贝
    • 生成固定范围数组
      • np.linspace()
        • num – 生成等间隔的多少个
      • np.arange()
        • step – 每间隔多少生成数据
      • np.logspace()
        • 生成以10为底的N次幂的数据
    • 生成随机数组
      • 正态分布
        • 均值: μ \mu μ
        • 标准差: σ \sigma σ
        • μ \mu μ – 决定了这个图形的左右位置
        • σ \sigma σ – 决定了这个图形是瘦高还是矮胖
        • 方法1:np.random.randn()
        • 方法2:np.random.normal(0, 1, 100)
      • 均匀分布
        • 方法1:np.random.rand()
        • 方法2:np.random.uniform(0, 1, 100)
        • 方法3:np.random.randint(0, 10, 10)
  • 数组索引【知道】
    • 直接进行索引,切片
    • 对象[:, :] – 先行后列
  • 数组形状改变【掌握】
    • ndarray.reshape(new_shape) -> ndarray
      • 不修改原来的ndarray
      • 深拷贝
    • ndarray.resize() -> None
      • 修改原来的ndarray
      • 浅拷贝
    • ndarray.T -> view
      • 不修改原来的ndarray
      • 浅拷贝
  • 数组去重【知道】
    • np.unique(ndarray)

3.4 ndarray的运算

学习目标:

  1. 应用数组的通用判断函数
  2. 应用np.where实现数组的三元运算

Q:如果想要操作符合某一条件的ndarray数据,应该怎么做?
A:接下来要学习的内容就是解决这一问题的。

3.4.1 逻辑运算

语法:ndarray > int

示例:

np.random.seed(10086)

# 生成10名同学,5门功课的数据
score = np.random.randint(40, 100, size=[10, 5])


# 取出最后4名同学的成绩,用于逻辑判断
test_score = score[-4:, :]
print(test_score)
"""
[[77 90 92 53 59]
 [99 61 78 45 46]
 [47 82 49 53 49]
 [85 95 87 68 41]] 
"""


# bool赋值,将满足条件的设置为指定的值 -> bool索引
condiction_idx = test_score > 60
print(condiction_idx)
print(type(condiction_idx))  # 
print(condiction_idx.dtype)  # bool
"""
[[ True  True  True False False]
 [ True  True  True False False]
 [False  True False False False]
 [ True  True  True  True False]]
"""

test_score[test_score > 60] = 1
print(test_score)
"""
[[ 1  1  1 53 59]
 [ 1  1  1 45 46]
 [47  1 49 53 49]
 [ 1  1  1  1 41]]
"""

3.4.2 通用判断函数

通用判断函数有两个:

  1. np.all() -> bool:所有元素满足条件则返回True,否则返回False
  2. np.any() -> bool:任意元素满足条件则返回True,否则返回False

注意:

  • axis=0:先将ndarray展平后,按单个元素进行操作
  • axis=1:按行进行操作
  • axis=2:按列进行操作

上面这个说法并不是很准确,因为进行统计的时候,axis轴的取值并不一定,Numpy中不同的API轴的值都不一样,有的函数axis=0代表行,而有的函数axis=0代表列。因此在用之前我们需要自己查一查或自己动手看看效果。

怎么看行怎么看列:其实很简单,如果是按行计算,那么应该返回行数量个结果;如果按列计算,那么应该返回列数量个结果。

示例:

print(score)
"""
[[96 50 76 94 44]
 [97 48 47 98 79]
 [91 85 47 76 70]
 [97 75 75 74 77]
 [73 48 58 71 94]
 [69 44 92 87 65]
 [ 1  1  1 53 59]
 [ 1  1  1 45 46]
 [47  1 49 53 49]
 [ 1  1  1  1 41]]
"""


# 1. 判断前两名同学的成绩是否全及格 -> np.all()
res = np.all(score[:2, :] > 60)
print(res)  # False


# 2. 判断前两名同学的成绩是否有大于90分的 -> np.any()
res = np.any(score[:2, :] > 90)  # axis=None —— 按单个元素进行(将数组展平)
print(res)  # True

res = np.any(score[:2, :] > 90, axis=0)  # —— 按行进行
print(res)  # [ True False False  True False]

res = np.any(score[:2, :] > 90, axis=1)  # —— 按列进行
print(res)  # [ True  True]

3.4.3 np.where(三元运算符)

通过使用np.where()函数能够进行更加复杂的运算。

np.where() 函数有两种用法。

  • 第一种用法是 np.where(condition, x, y) -> ndarray
    • 参数:
      • condition 是一个布尔数组
      • xy 是两个数组
    • 作用:返回相同的数组,元素均被x, y替换
    • condition 中的元素为 True 时,返回 x 中对应位置的元素;
    • condition 中的元素为 False 时,返回 y 中对应位置的元素。
  • 第二种用法是 np.where(condition) -> tuple
    • 参数:
      • condition是一个布尔数组。
    • 作用:返回一个元组,其中包含满足条件(即值为 True)的元素的索引。
    • 返回值说明:返回一个元组tuple(ndarray1, ndarray2, ...)
      • 第一个数组表示第一维的索引(行)
      • 第二个数组表示第二维的索引(列)

[拓展]复合逻辑:结合np.logical_andnp.logical_or使用

np.logical_and 函数接受两个参数 x1x2,它们都是数组。该函数返回一个布尔数组,其形状与 x1x2 相同。返回数组中的每个元素都是对应位置的 x1x2 元素的逻辑与运算结果。例如,如果 x1x2 都是一维数组,那么返回数组中的第 i 个元素为 x1[i] and x2[i] 的结果。

np.logical_or函数同理。

示例:

np.random.seed(10086)
score = np.random.randint(40, 100, size=[5, 5])
print(score)
"""
[[96 50 76 94 44]
 [97 48 47 98 79]
 [91 85 47 76 70]
 [97 75 75 74 77]
 [73 48 58 71 94]]
"""

# 1. np.where(condition, x, y):返回相同的数组,元素均被x,y替换
res = np.where(score[:2, :] > 85, "G", "P")
print(res)
"""
[['G' 'P' 'P' 'G' 'P']
 ['G' 'P' 'P' 'G' 'P']]
"""


# 2. np.where(condition):返回一个元组,其中包含满足条件(即值为 `True`)的元素的索引。
res = np.where(score < 80)
print(res)
"""
(
    array([0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4], dtype=int64), 
    array([1, 2, 4, 1, 2, 4, 2, 3, 4, 1, 2, 3, 4, 0, 1, 2, 3], dtype=int64)
)
其中,第一个数组表示行索引,第二个数组表示列索引。

如果 score 是一个三维数组,那么 res = np.where(score < 80) 的结果仍然是一个元组,
其中包含三个数组:
    第一个数组表示第一维的索引
    第二个数组表示第二维的索引
    第三个数组表示第三维的索引。
"""


# 拓展:结合np.logical_and和np.logical_or使用
res = np.where(np.logical_and(score > 60, score < 90), "G", "P")
print(res)
"""
[['P' 'P' 'G' 'P' 'P']
 ['P' 'P' 'P' 'P' 'G']
 ['P' 'G' 'P' 'G' 'G']
 ['P' 'G' 'G' 'G' 'G']
 ['G' 'P' 'P' 'G' 'P']]
"""

res = np.where(np.logical_or(score > 60, score[:, 1:2] > 70), "G", "P")
print(res)
"""
[['G' 'P' 'G' 'G' 'P']
 ['G' 'P' 'P' 'G' 'G']
 ['G' 'G' 'G' 'G' 'G']
 ['G' 'G' 'G' 'G' 'G']
 ['G' 'P' 'P' 'G' 'G']]
"""

res = np.where(np.logical_and(score > 60, score < 90))
print(res)
"""
(array([0, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4], dtype=int64), 
array([2, 4, 1, 3, 4, 1, 2, 3, 4, 0, 3], dtype=int64))
"""

res = np.where(np.logical_or(score >60, score[:, 1:2] > 70))
print(res)
"""
(array([0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4], dtype=int64), 
array([0, 2, 3, 0, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 3, 4], dtype=int64))
"""

3.5 统计运算

3.5.1 统计指标及其API

在数据挖掘/机器学习领域,统计指标的值也是我们分析问题的一种方式。常用的指标如下:

  1. min(a, axis):返回数组的最小值或沿轴的最小值。
  2. max(a, axis):返回数组的最大值或沿轴的最大值。
  3. median(a, axis):沿指定轴计算中位数。
  4. mean(a, axis, dtype):沿指定轴计算算术平均值。
  5. std(a, axis, dtype):沿指定轴计算标准差。
  6. var(a, axis, dtype):沿指定轴计算方差。
  7. np.argmax(a, axis):返回数组的最大值或沿轴的最大值元素的索引。
  8. np.argmin(a, axis):返回数组的最小值或沿轴的最小值元素的索引。

标准差(Standard Deviation)的缩写是 std
方差(Variance)的缩写是 var

Deviation:偏离; 偏差
Variables:变量
Variance:方差
Variant:变体

3.5.2 案例:学生成绩统计

进行统计的时候,axis轴的取值并不一定,Numpy中不同的API轴的值都不一样,
在这里,axis=0代表列,axis=1代表行去进行统计。

Q:怎么看行怎么看列?
A:其实很简单,如果是按行计算,那么应该返回行数量个结果;如果按列计算,那么应该返回列数量个结果。

示例:

import os

def print_separator(char='-'):
    terminal_width = os.get_terminal_size().columns
    print(char * terminal_width)

np.random.seed(10086)
score = np.random.randint(40, 100, size=[4, 5])
print(score)
"""
[[96 50 76 94 44]
 [97 48 47 98 79]
 [91 85 47 76 70]
 [97 75 75 74 77]]
"""

np.set_printoptions(precision=2)  # 设置numpy的精度
print("各科成绩的最大值分别为: {}".format(np.max(score, axis=0)))  # [97 85 76 98 79]
print("各科成绩的最小值分别为: {}".format(np.min(score, axis=0)))  # [91 48 47 74 44]
print("各科成绩的平均值分别为: {}".format(np.mean(score, axis=0)))  # [95.25 64.5  61.25 85.5  67.5 ]
print("各科成绩的波动情况为: {}".format(np.std(score, axis=0)))  # [ 2.49 15.91 14.25 10.62 13.97]
print("各科成绩的波动情况^2为: {}".format(np.var(score, axis=0)))  # [  6.19 253.25 203.19 112.75 195.25]

print_separator()

print("学生的最大成绩分别为: {}".format(np.max(score, axis=1)))  # [96 98 91 97]
print("学生的最小成绩分别为: {}".format(np.min(score, axis=1)))  # [44 47 47 74]
print("学生的最大成绩分别为: {}".format(np.mean(score, axis=1)))  # [72.  73.8 73.8 79.6]
print("学生的偏科情况分别为: {}".format(np.std(score, axis=1)))  # [21.65 22.52 15.22  8.75]
print("学生的偏科情况^2分别为: {}".format(np.var(score, axis=1)))  # [468.8  506.96 231.76  76.64]


"""
进行统计的时候,axis轴的取值并不一定,Numpy中不同的API轴的值都不一样,
在这里,axis=0代表列,axis=1代表行去进行统计。

Q:怎么看行怎么看列?
A:其实很简单,如果是按行计算,那么应该返回行数量个结果;如果按列计算,那么应该返回列数量个结果。
"""

print_separator()

print("各科成绩最高的学生的索引分别为: {}".format(np.argmax(score, axis=0)))  # [1 2 0 1 1]
print("各科成绩最低的学生的索引分别为: {}".format(np.argmin(score, axis=0)))  # [2 1 1 3 0]

print_separator()

print("学生分数最高的科目分别为: {}".format(np.argmin(score, axis=1)))  # [4 2 2 3]

对于学生的各科成绩,均值(mean)表示所有成绩的平均值,它可以用来衡量学生的整体表现。方差(variance)表示各科成绩与均值之差的平方的平均值,它可以用来衡量学生各科成绩的离散程度。方差越大,说明学生各科成绩之间的差异越大;方差越小,说明学生各科成绩之间的差异越小。


小结

  • 逻辑运算【知道】
    • 直接进行大于,小于的判断
    • 合适之后,可以直接进行赋值
  • 通用判断函数【知道】
    • np.all()
    • np.any()
  • 统计运算【掌握】
    • np.max()
    • np.min()
    • np.median()
    • np.mean()
    • np.std()
    • np.var()
    • np.argmax(axis=):返回最大元素对应的下标
    • np.argmin(axis=):返回最小元素对应的下标

3.6 数组间的运算

学习目标:

  1. 知道数组与数之间的运算
  2. 知道数组与数组之间的运算
  3. 说明数组间运算的广播机制

3.6.1 数组与数的运算

我们先看一下Python中的list与数的运算。

# 先看一下Python中list的运算
lst = [1, 2, 3]
# print(lst + 3)  # TypeError: can only concatenate list (not "int") to list
# print(lst - 3)  # TypeError: unsupported operand type(s) for -: 'list' and 'int'
print(lst * 3)  # [1, 2, 3, 1, 2, 3, 1, 2, 3]
print(lst / 3)  # TypeError: unsupported operand type(s) for /: 'list' and 'int'

可以看到,Python的list不能和int进行±/,只能进行*,即将list中的元素再扩充int倍。

结论:Python的列表支持加法和乘法运算,对减法和除法不支持:

  • 加法只能两个list相加
  • 乘法是list和int相乘

Numpy的ndarray与list不同,ndarray是支持基本的与int数据类型的加减乘除的。

示例:

arr = np.array([[1, 2, 3], [3, 4, 5]])
print(arr)
"""
[[1 2 3]
 [3 4 5]]
"""

print(arr + 3)
"""
[[4 5 6]
 [6 7 8]]
"""

print(arr - 3)
"""
[[-2 -1  0]
 [ 0  1  2]]
"""

print(arr * 3)
"""
[[ 3  6  9]
 [ 9 12 15]]
"""

print(arr / 3)
"""
[[0.33333333 0.66666667 1.        ]
 [1.         1.33333333 1.66666667]]
"""

3.6.2 数组与数组的运算

arr1 = np.array([[1, 2, 3, 2, 1, 4], [5, 6, 1, 2, 3, 1]])
arr2 = np.array([[1, 2, 3, 4], [3, 4, 5, 6]])

print(arr1.shape)  # (2, 6)
print(arr2.shape)  # (2, 4)

# print(arr1 + arr2)  # ValueError: operands could not be broadcast together with shapes (2,6) (2,4) 
# print(arr1 - arr2)  # ValueError: operands could not be broadcast together with shapes (2,6) (2,4) 
# print(arr1 * arr2)  # ValueError: operands could not be broadcast together with shapes (2,6) (2,4) 
# print(arr1 / arr2)  # ValueError: operands could not be broadcast together with shapes (2,6) (2,4) 

可以看到,两个数组的shape不同,不能进行加减乘除,为了解决这个问题,我们需要了解Numpy的广播机制。

3.6.3 广播机制

数组在进行矢量化运算的,要求数组的形状是相等的。当形状不相等的数组执行算术运算的时候,就会出现广播机制,该机制会对数组进行扩展,使数组的shape属性值一样,这样,就可以进行矢量化运算了。

下面通过一个例子进行说明:

arr1 = np.array([[0], [1], [2], [3]])
arr2 = np.array([1, 2, 3])

print(arr1.shape)  # (4, 1)
print(arr2.shape)  # (3,)

print(arr1 + arr2)
"""
[[1 2 3]
 [2 3 4]
 [3 4 5]
 [4 5 6]]
"""

print(arr1 - arr2)
"""
[[-1 -2 -3]
 [ 0 -1 -2]
 [ 1  0 -1]
 [ 2  1  0]]
"""

print(arr1 * arr2)
"""
[[0 0 0]
 [1 2 3]
 [2 4 6]
 [3 6 9]]
"""

print(arr1 / arr2)
"""
[[0.         0.         0.        ]
 [1.         0.5        0.33333333]
 [2.         1.         0.66666667]
 [3.         1.5        1.        ]]
"""

上述代码中,数组arr1是4行1列,arr2是1行3列。这两个数组要进行相加,按照广播机制会对数组arr1arr2都进行扩展,使得数组arr1arr2都变成4行3列。

下面通过一张图来描述广播机制扩展数组的过程:

[学习笔记] 1. 机器学习前置知识_第15张图片


广播机制实现了时两个或两个以上数组的运算,即使这些数组的shape不是完全相同的,只需要每个维度满足如下任意一个条件即可。

  1. 维度长度相等。
  2. 有一个的长度为1。

两个数组的每一维度的长度要么相等,要么其中一个数组的长度为1

广播机制需要扩展维度小的数组,使得它与维度最大的数组的shape值相同,以便使用元素级函数或者运算符进行运算。

如果是下面这样,则不匹配:

A (1d array): 10
B (1d array): 12

A (2d array):     2 × 1
B (2d array): 8 × 4 × 3

以上都不可以。

[学习笔记] 1. 机器学习前置知识_第16张图片

对于后面两个数组,从后往前看,1, 3是可以的,但2, 4不行,所以不行。

思考:下面两个ndarray是否能够进行运算?

arr1 = np.array([[1, 2, 3, 2, 1, 4], [5, 6, 1, 2, 3, 1]])
arr2 = np.array([[1], [3]])

print(arr1.shape)  # (2, 6)
print(arr2.shape)  # (2, 1)

[学习笔记] 1. 机器学习前置知识_第17张图片

可以进行运算!


小结:

  • 数组运算,满足广播机制。就OK【知道】。
    • 每个维度相等或维度为1

3.7 矩阵

矩阵,英文Matrix,和array的区别是:

  • 矩阵必须是2维的
  • 但是array可以是多维的

如图:这个是3×2矩阵,即3行2列,如 m m m为行, n n n为列,那么 m × n m\times n m×n即3×2。

[ 1 2 3 4 5 6 ] \begin{bmatrix} 1 & 2 \\ 3 & 4 \\ 5 & 6 \end{bmatrix} 135246

矩阵的维数即行数×列数。

矩阵元素(矩阵项):

A = [ 1 2 3 4 5 6 ] A=\begin{bmatrix} 1 & 2 \\ 3 & 4 \\ 5 & 6 \end{bmatrix} A= 135246

A i j A_{ij} Aij 指第 i i i行,第 j j j列的元素。

3.8 向量

向量是一种特殊的矩阵,讲义中的向量一般都是列向量,下面展示的就是三维列向量(3×1)。

A = [ 1 2 3 ] A = \begin{bmatrix} 1 \\ 2 \\ 3 \\ \end{bmatrix} A= 123

3.9 矩阵的加法和标量乘法

矩阵的加法:行列数相等的可以加。

例:

[ 1 2 3 4 5 6 ] + [ 1 2 3 4 5 6 ] = [ 2 4 6 8 10 12 ] \begin{bmatrix} 1 & 2\\ 3 & 4\\ 5 & 6\\ \end{bmatrix}+ \begin{bmatrix} 1 & 2\\ 3 & 4\\ 5 & 6\\ \end{bmatrix}= \begin{bmatrix} 2 & 4\\ 6 & 8\\ 10 & 12\\ \end{bmatrix} 135246 + 135246 = 26104812

矩阵的乘法:每个元素都要乘。

例:

3 × [ 1 2 3 4 5 6 ] = [ 3 6 9 12 15 18 ] 3 \times \begin{bmatrix} 1 & 2\\ 3 & 4\\ 5 & 6\\ \end{bmatrix}= \begin{bmatrix} 3 & 6\\ 9 & 12\\ 15 & 18\\ \end{bmatrix} 3× 135246 = 391561218

组合算法也是类似的。

3.10 矩阵向量的乘法

矩阵和向量的乘法如图: m × n m\times n m×n的矩阵乘以 n × 1 n\times 1 n×1的向量,得到的是 m × 1 m \times 1 m×1 的向量。

例:

[ 1 3 4 0 2 1 ] 3 × 2 × [ 1 5 ] 2 × 1 = [ 16 4 7 ] 3 × 1 \begin{bmatrix} 1 & 3 \\ 4 & 0 \\ 2 & 1 \\ \end{bmatrix}_{3\times 2} \times \begin{bmatrix} 1 \\ 5 \end{bmatrix}_{2\times 1} = \begin{bmatrix} 16\\ 4\\ 7 \end{bmatrix}_{3\times 1} 142301 3×2×[15]2×1= 1647 3×1

1 * 1 + 3 * 5 = 16
4 * 1 + 0 * 5 = 4
2 * 1 + 1 * 5 = 7

矩阵乘法遵循准则:

( M 行 , N 列 ) × ( N 行 , L 列 ) = ( M 行 , L 列 ) (M行, N列) \times (N行, L列) = (M行, L列) (M,N)×(N,L)=(M,L)

C = A × B [ C 0 C 1 C 2 C 3 ] = [ A 0 A 1 A 2 A 3 ] × [ B 0 B 1 B 2 B 3 ] C 0 = A 0 × B 0 + A 1 × B 2 C 1 = A 0 × B 1 + A 1 × B 3 C 2 = A 2 × B 0 + A 3 × B 2 C 3 = A 2 × B 1 + A 3 × B 3 C = A \times B \\ \begin{bmatrix} C_0 & C_1 \\ C_2 & C_3 \end{bmatrix} = \begin{bmatrix} A_0 & A_1 \\ A_2 & A_3 \end{bmatrix} \times \begin{bmatrix} B_0 & B_1 \\ B_2 & B_3 \end{bmatrix}\\ C_0 = A_0 \times B_0 + A_1 \times B_2 \\ C_1 = A_0 \times B_1 + A_1 \times B_3 \\ C_2 = A_2 \times B_0 + A_3 \times B_2 \\ C_3 = A_2 \times B_1 + A_3 \times B_3 \\ C=A×B[C0C2C1C3]=[A0A2A1A3]×[B0B2B1B3]C0=A0×B0+A1×B2C1=A0×B1+A1×B3C2=A2×B0+A3×B2C3=A2×B1+A3×B3

3.11 矩阵乘法的性质

  • 矩阵的乘法不满足交换律: A × B ≠ B × A A\times B \neq B \times A A×B=B×A
  • 矩阵的乘法满足结合律: A × ( B × C ) = ( A × B ) × C A \times (B \times C) = (A\times B) \times C A×(B×C)=(A×B)×C

单位矩阵:在矩阵的乘法中,有一种矩阵起着特殊的作用,如同 数的乘法中 的1,我们称这种矩阵为单位矩阵。单位矩阵是个方阵,一般用 I I I或者 E E E表示,从左上角到右下角的对角线(称为主对角线)上的元素均为1,剩下的全为0。如:

[ 1 0 0 1 ] 2 × 2      [ 1 0 0 0 1 0 0 0 10 ] 3 × 3      [ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 10 ] 4 × 4 \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix}_{2 \times 2} \ \ \ \ \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 0\end{bmatrix}_{3 \times 3} \ \ \ \ \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 0\end{bmatrix}_{4 \times 4} [1001]2×2     1000100010 3×3     10000100001000010 4×4

3.12 矩阵的逆和转置

一、矩阵的逆:如矩阵 A A A 是一个 m × m m\times m m×m 矩阵(方阵),如果有逆矩阵,则:

A A − 1 = A − 1 A = E ( 单位矩阵 ) AA^{-1} = A^{-1}A = E(单位矩阵) AA1=A1A=E(单位矩阵)

低阶矩阵求逆的方法(了解):

  1. 待定系数法
  2. 初等变换

二、矩阵的转置:设 A A A m × n m\times n m×n 阶矩阵(即 m m m n n n 列),第 i i i j j j 列的元素是 a ( i , j ) a(i,j) a(i,j),即:

A = a ( i , j ) A = a(i, j) A=a(i,j)

定义 A A A的转置为这样一个 n × m n\times m n×m 阶矩阵 B B B,满足 B = a ( i , j ) B=a(i,j) B=a(i,j),即 b ( i , j ) = a ( j , i ) b(i,j)=a(j,i) b(i,j)=a(j,i) B B B 的第 i i i 行第 j j j 列元素是 A A A 的第 j j j 行第 i i i 列元素),记 A T = B A^T=B AT=B

直观来看,将 A A A 的所有元素绕着一条从第 1 1 1 行第 1 1 1 列元素出发的右下方45度的射线作镜面反转,即得到 A A A的转置。

例:

[ a b c d e f ] 3 × 2 T = [ a c e b d f ] 2 × 3 \begin{bmatrix} a & b \\ c & d \\ e & f \end{bmatrix}^T_{3 \times 2} = \begin{bmatrix} a & c & e \\ b & d & f \end{bmatrix}_{2 \times 3} acebdf 3×2T=[abcdef]2×3

3.13 矩阵乘法的日常应用举例

[学习笔记] 1. 机器学习前置知识_第18张图片

平时成绩占70%,期末成绩占30%,如何使用矩阵乘法求出最终成绩?

[ 80 86 82 80 85 78 90 90 86 82 82 90 78 80 92 94 ] 8 × 2 × [ 0.7 0.3 ] 2 × 1 = [ 84.2 80.6 80.1 90.0 83.2 87.6 79.4 93.4 ] 8 × 1 \begin{bmatrix} 80 & 86 \\ 82 & 80 \\ 85 & 78 \\ 90 & 90 \\ 86 & 82 \\ 82 & 90 \\ 78 & 80 \\ 92 & 94 \\ \end{bmatrix}_{8 \times 2} \times \begin{bmatrix} 0.7 \\ 0.3 \\ \end{bmatrix}_{2 \times 1} = \begin{bmatrix} 84.2 \\ 80.6 \\ 80.1 \\ 90.0 \\ 83.2 \\ 87.6 \\ 79.4 \\ 93.4 \\ \end{bmatrix}_{8 \times 1} 80828590868278928680789082908094 8×2×[0.70.3]2×1= 84.280.680.190.083.287.679.493.4 8×1

3.14 Numpy中的矩阵乘法

在numpy中,矩阵乘法有两个API:

  1. np.matmul(arr1, arr2, out)
  • 作用:矩阵乘法的函数
  • 参数说明:
    • arr1arr2 是输入数组,必须是类似数组的对象。
    • out 是一个可选参数,它是一个 n n n 维数组,用于存储输出结果
  1. np.dot(vector_a, vector_b, out)
  • 作用:计算点积和矩阵乘法的函数
  • 参数说明:
    • vector_avector_b 是输入向量
    • out 是一个可选参数,它是一个 n 维数组,用于存储输出结果

np.matmulnp.dot的区别:

  • 二者都是矩阵乘法,在进行矩阵×矩阵时二者没有区别。
  • np.matmul中禁止矩阵与标量的乘法。
  • 在矢量乘矢量的内积运算中,np.matmulnp.dot没有区别。

示例:

a = np.array([[80, 86], [82, 80], [85, 78], [90, 90], [86, 82], [82, 90], [78, 80], [92, 94]])
b = np.array([[0.7], [0.3]])

print(a.shape)  # (8, 2)
print(b.shape)  # (2, 1)

# print(a * b)  # ValueError: operands could not be broadcast together with shapes (8,2) (2,1) 
res1 = np.matmul(a, b)
print(res1, res1.shape)
"""
[[81.8]
 [81.4]
 [82.9]
 [90. ]
 [84.8]
 [84.4]
 [78.6]
 [92.6]] (8, 1)
"""

res2 = np.dot(a, b)
print(res2, res2.shape)
"""
[[81.8]
 [81.4]
 [82.9]
 [90. ]
 [84.8]
 [84.4]
 [78.6]
 [92.6]] (8, 1)
"""


# np.matmul不支持矩阵与标量的乘法
c = 10
# res3 = np.matmul(a, c)  # ValueError: matmul: Input operand 1 does not have enough dimensions (has 0, gufunc core with signature (n?,k),(k,m?)->(n?,m?) requires 1)

# np.dot支持矩阵与标量的乘法
c = 10
res3 = np.dot(a, c)
print(res3, res3.shape)
"""
[[800 860]
 [820 800]
 [850 780]
 [900 900]
 [860 820]
 [820 900]
 [780 800]
 [920 940]] (8, 2)
"""

小结

  1. 矩阵和向量【知道】
    • 矩阵就是特殊的二维数组
    • 向量就是一行或者一列的数据
  2. 矩阵加法和标量乘法【知道】
    • 矩阵的加法:行列数相等的可以加
    • 矩阵的乘法:每个元素都要乘
  3. 矩阵和矩阵(向量)相乘【知道】
    • ( M 行 , N 列 ) × ( N 行 , L 列 ) = ( M 行 , L 列 ) (M行, N列) \times (N行, L列) = (M行, L列) (M,N)×(N,L)=(M,L)
  4. 矩阵性质【知道】
    • 矩阵不满足交换率,但满足结合律
  5. 单位矩阵【知道】
    • 对角线都是 1 1 1,其他位置都为 0 0 0的矩阵
  6. 矩阵运算【掌握】
    • np.matmul()
    • np.dot()
    • 注意:二者都是矩阵乘法
    • np.matmul禁止矩阵与标量的乘法
    • 在矢量乘矢量的内积运算中,np.matmul()np.dot()没有区别

3.15 [扩展]shape=(3, ), shape=(3, 1), shape=(1, 3)的区别

形状为(3,)和形状为(3, 1)的数组在NumPy中被认为是不同的,它们的数据类型不同,且在进行一些运算时可能会有不同的表现。

  • 形状为(3,)的数组是一维数组,通常被称为行向量或列向量,但是在NumPy中是行向量。这种数组只有一个轴,其中的元素按照一维数组的方式排列,例如[1, 2, 3]

  • 形状为(3, 1)的数组是二维数组,其中第一维度的长度为3,第二维度的长度为1,通常被称为列向量。这种数组有两个轴,其中的元素按照二维数组的方式排列,例如[[1], [2], [3]]

  • 形状为(1, 3)的数组也是二维数组,其中第一维度的长度为1,第二维度的长度为3,通常被称为行向量。这种数组有两个轴,其中的元素按照二维数组的方式排列,例如[[1, 2, 3]]

在进行一些运算时,这些数组的表现可能会有不同。例如:

  • 对于形状为(3,)的数组,它的转置仍然是(3,)的行向量;
  • 对于形状为(3, 1)的数组,它的转置是(1, 3)的行向量;
  • 对于形状为(1, 3)的数组,它的转置是(3, 1)的列向量。

因此,在使用这些数组时,需要根据具体的情况来选择合适的形状和操作。

示例代码:

import numpy as np

a = np.array([1, 2, 3])  # 形状为(3,)
b = np.array([[1], [2], [3]])  # 形状为(3, 1)
c = np.array([[1, 2, 3]])  # 形状为(1, 3)

print(a.T)  # [1 2 3]
print(b.T)  # [[1 2 3]]
print(c.T)  # [[1], [2], [3]]

上述示例代码中,分别定义了三个不同形状的数组,分别对它们进行了转置操作,并打印了转置后的结果。可以看出,转置操作对于不同形状的数组,得到的结果也是不同的。

4. Pandas

  • 2008年WesMcKinney开发的库,专门用于数据挖掘的开源Python库
  • 以Numpy为基础,借力Numpy模块在计算方面性能高的优势
  • 基于Matplotlib,能够简便的画图
  • 独特的数据结构

Numpy已经能够帮助我们处理数据,能够结合Matplotlib解决部分数据展示等问题,那么pandas学习的目的在什么地方呢?

  • 增强图表的可读性
  • 便捷的数据处理能力
  • 方便读取文件
  • 封装了Matplotlib和Numpy的画图和计算优势

Pandas中一共有三种数据结构,分别为:

  1. Series:一维数据结构
  2. DataFrame:二维的表格型数据结构
  3. MultiIndex(老版本中叫Panel):三维的数据结构

学习目标:

  1. 了解Numpy与Pandas的不同
  2. 说明Pandas的SeriesDataframe两种结构的区别
  3. 了解Pandas的MultiIndexpanel结构
  4. 应用Pandas实现基本数据操作
  5. 应用Pandas实现数据的合并
  6. 应用crosstabpivot_table实现交叉表与透视表
  7. 应用groupby和聚合函数实现数据的分组与聚合
  8. 了解Pandas的plot画图功能
  9. 应用Pandas实现数据的读取和存储

4.1 Series数据结构

Series是一个类似于一维数组的数据结构,它能够保存任何类型的数据,比如整数、字符串、浮点数等,主要由一组数据和与之相关的索引两部分构成。

[学习笔记] 1. 机器学习前置知识_第19张图片

4.1.1 Series的创建

import pandas as pd

pd.Series(data=None, index=None, dtype=None)
  • 参数:
    • data:传入的数据,可以是ndarray、list等
    • index:索引,必须是唯一的,且与数据的长度相等。如果没有传入索引参数,则默认会自动创建一个从0-N的整数索引。
    • dtype:数据的类型
  • 可以通过已有数据创建

Series的创建方式一般有三种:

  1. 指定内容,默认索引
  2. 指定内容,指定索引
  3. 通过字典数据创建

一、指定内容,默认索引

pd.Series(np.arange(10))

"""
0    0
1    1
2    2
3    3
4    4
5    5
6    6
7    7
8    8
9    9
dtype: int32
"""

二、指定内容,指定索引

pd.Series([6.7, 5.6, 3, 10, 2], index=[1, 2, 3, 4, 5])

"""
1     6.7
2     5.6
3     3.0
4    10.0
5     2.0
dtype: float64
"""

三、通过字典数据创建

color_count = pd.Series({"red": 100, "blue": 200, "green": 500, "yellow": 1000})

color_count

"""
red        100
blue       200
green      500
yellow    1000
dtype: int64
"""

4.1.2 Series的属性

为了更方便地操作Series对象中的索引和数据,Series中提供了两个属性indexvalues

示例:

color_count = pd.Series({"red": 100, "blue": 200, "green": 500, "yellow": 1000})

print(color_count.index)  # Index(['red', 'blue', 'green', 'yellow'], dtype='object')
print(color_count.values)  # [ 100  200  500 1000]

# 读取Series的元素
print(color_count["red"])  # 100
print(color_count["blue"])  # 200
print(color_count["green"])  # 500
print(color_count["yellow"])  # 1000

# Series也可以通过int索引来取值
print(color_count[0])  # 100
print(color_count[1])  # 200
print(color_count[2]) # 500
print(color_count[3])  # 1000

Q:pandas的Series可以理解为是一种容器吗?
A:Pandas 的 Series 可以被理解为一种容器,它可以存储不同类型的数据。Series 是 Pandas 中的一维数组,它可以存储整数、浮点数、字符串、Python 对象等类型的数据。Series 具有与 NumPy 数组类似的功能,但它还具有轴标签,这意味着它可以通过索引标签来访问数据。

4.2 Dataframe数据结构

DataFrame是一个类似于二维数组或表格(如excel)的对象,既有行索引,又有列索引。

  • 行索引,表明不同行,横向索引,叫index,0轴,axis=O
  • 列索引,表明不同列,纵向索引,叫columns,1轴,axis=1

[学习笔记] 1. 机器学习前置知识_第20张图片

4.2.1 DataFrame的创建

pd.DataFrame(data=None, index=None, columns=None)

参数:

  • 参数:
    • index:行标签。如果没有传入索引参数,则默认会自动创建一个从0-N的整数索引。
    • columns:列标签。如果没有传入索引参数,则默认会自动创建一个从0-N的整数索引。
  • 可以通过已有数据创建

示例1:随机创建一个2行3列的DataFrame对象

tmp = pd.DataFrame(np.random.randn(2, 3))
print(tmp)

"""
          0         1         2
0  1.248340  0.921399  0.651492
1  0.506594  0.944270 -0.411782
"""

示例2:创建学生成绩表

score = np.random.randint(40, 100, (10, 5))
print(score)
"""
[[74 98 74 40 94]
 [52 63 72 54 50]
 [54 90 92 70 61]
 [71 44 61 70 99]
 [88 79 57 64 95]
 [51 83 47 71 76]
 [70 42 48 90 44]
 [95 54 48 66 40]
 [55 65 59 94 65]
 [81 91 85 61 91]]
"""

但是这样的数据形式很难看到存储的是什么的样的数据,可读性比较差!

我们可以使用Pandas使得数据更加直观的显示:

score_df = pd.DataFrame(score)
print(score_df)

"""
    0   1   2   3   4
0  74  98  74  40  94
1  52  63  72  54  50
2  54  90  92  70  61
3  71  44  61  70  99
4  88  79  57  64  95
5  51  83  47  71  76
6  70  42  48  90  44
7  95  54  48  66  40
8  55  65  59  94  65
9  81  91  85  61  91
"""

# 增加行、列索引
subjects = ["语文", "数学", "英语", "政治", "体育"]

# 构造列表索引序列
stu = ["同学{}".format(i) for i in range(score_df.shape[0])]

# 添加行索引
data = pd.DataFrame(score, index=stu, columns=subjects)

print(data)
"""
      语文  数学  英语  政治  体育
同学0  74    98    74    40    94
同学1  52    63    72    54    50
同学2  54    90    92    70    61
同学3  71    44    61    70    99
同学4  88    79    57    64    95
同学5  51    83    47    71    76
同学6  70    42    48    90    44
同学7  95    54    48    66    40
同学8  55    65    59    94    65
同学9  81    91    85    61    91
"""

4.2.2 DataFrame的属性

  1. .shape:返回一个tuple,(行,列)
  2. .index:返回一个Index()对象,里面存放一个行索引的list
  3. .columns:返回一个Index()对象,里面存放一个列索引的list
  4. .values:返回一个array()对象,里面存放一个值的list
  5. .T:转置
  6. .head(n=5):返回前n行数据 —— 这个属于方法了,所以要加()
  7. tail(n=5):返回倒数n行数据 —— 这个属于方法了,所以要加()

示例:

# 1. .shape:返回一个tuple,(行,列)
print(data.shape)  # (10, 5)

# 2. .index:返回一个Index()对象,里面存放一个行索引的list
print(data.index)  # Index(['同学0', '同学1', '同学2', '同学3', '同学4', '同学5', '同学6', '同学7', '同学8', '同学9'], dtype='object')

# 3. .columns:返回一个Index()对象,里面存放一个列索引的list
print(data.columns)  # Index(['语文', '数学', '英语', '政治', '体育'], dtype='object')

# 4. .values:返回一个array()对象,里面存放一个值的list
print(data.values)
print(type(data.values))  # 
"""
[[74 98 74 40 94]
 [52 63 72 54 50]
 [54 90 92 70 61]
 [71 44 61 70 99]
 [88 79 57 64 95]
 [51 83 47 71 76]
 [70 42 48 90 44]
 [95 54 48 66 40]
 [55 65 59 94 65]
 [81 91 85 61 91]]
"""

# 5. .T:转置
print(data.T)
"""
      同学0  同学1  同学2  同学3  同学4  同学5  同学6  同学7  同学8  同学9
语文   74     52     54     71     88      51    70     95     55     81
数学   98     63     90     44     79      83    42     54     65     91
英语   74     72     92     61     57      47    48     48     59     85
政治   40     54     70     70     64      71    90     66     94     61
体育   94     50     61     99     95      76    44     40     65     91
"""

# 6. .head(n=5):返回前n行数据
print(data.head())
"""
      语文  数学  英语  政治  体育
同学0  74    98    74    40    94
同学1  52    63    72    54    50
同学2  54    90    92    70    61
同学3  71    44    61    70    99
同学4  88    79    57    64    95
"""

# 7. tail(n=5):返回倒数n行数据
print(data.tail())
"""
      语文  数学  英语  政治  体育
同学5  51    83    47    71    76
同学6  70    42    48    90    44
同学7  95    54    48    66    40
同学8  55    65    59    94    65
同学9  81    91    85    61    91
"""

4.2.3 DataFrame索引的设置

注意:

  1. DF.index是支持索引访问的(如df.index[0]),但不支持修改,即可以访问单个索引,但不可以修改单个索引
  2. DF.index这个整体是可以修改的!(要修改一定要全修改,否则会报错!)

一、修改行列索引值

需求:将"同学0"改为"学生_0",该如何操作?

示例:

print(data)
"""
      语文  数学  英语  政治  体育
同学0  74    98    74    40    94
同学1  52    63    72    54    50
同学2  54    90    92    70    61
同学3  71    44    61    70    99
同学4  88    79    57    64    95
同学5  51    83    47    71    76
同学6  70    42    48    90    44
同学7  95    54    48    66    40
同学8  55    65    59    94    65
同学9  81    91    85    61    91
"""

# 直接修改单个index的方式是错误的!
# data.index[3] = "学生_3"  # TypeError: Index does not support mutable operations

# 可以访问单个索引,但不可以修改单个索引
print(data.index[1])  # 学生_1

# DF.index[int]不是mutable属性,但DF.index这个整体是可以修改的!(要修改一定要全修改,否则会报错!)
# 方法1
data.index = ["学生_0", "学生_1", "学生_2", "学生_3", 
              "学生_4", "学生_5", "学生_6", "学生_7", 
              "学生_8", "学生_9"]

# 方法2
# stu = ["学生_" + str(i) for i in range(score_df.shape[0])]

data.index = stu

print(data)
"""
       语文  数学  英语  政治  体育
学生_0  74    98    74    40    94
学生_1  52    63    72    54    50
学生_2  54    90    92    70    61
学生_3  71    44    61    70    99
学生_4  88    79    57    64    95
学生_5  51    83    47    71    76
学生_6  70    42    48    90    44
学生_7  95    54    48    66    40
学生_8  55    65    59    94    65
学生_9  81    91    85    61    91
"""

二、重设索引

方法:reset_index(drop=False) -> DataFrame

  • 作用:将 DataFrame 索引重置为从 0 开始的默认整数索引。
  • 参数说明:
    • drop: 默认值为 False,表示是否在重置索引时丢弃原来的索引列。如果设置为 True,则会将原来的索引列删除。
    • level: 如果数据框是多级索引,可以指定需要重置的级别。
    • col_level: 如果数据框有多级列索引,则可以指定要求重置的级别。
    • col_fill: 如果数据框有多级列索引,则可以指定新列的名称。
  • 返回值:返回一个新的 DataFrame,其中包含重置后的索引。

示例:

# .reset_index方法对原来的DF没有影响
data.reset_index()

print(data)
"""
      语文  数学  英语  政治  体育
学生_0  74  98  74  40  94
学生_1  52  63  72  54  50
学生_2  54  90  92  70  61
学生_3  71  44  61  70  99
学生_4  88  79  57  64  95
学生_5  51  83  47  71  76
学生_6  70  42  48  90  44
学生_7  95  54  48  66  40
学生_8  55  65  59  94  65
学生_9  81  91  85  61  91
"""

print(data.reset_index())
"""
  index  语文  数学  英语  政治  体育
0  学生_0  74  98  74  40  94
1  学生_1  52  63  72  54  50
2  学生_2  54  90  92  70  61
3  学生_3  71  44  61  70  99
4  学生_4  88  79  57  64  95
5  学生_5  51  83  47  71  76
6  学生_6  70  42  48  90  44
7  学生_7  95  54  48  66  40
8  学生_8  55  65  59  94  65
9  学生_9  81  91  85  61  91
"""

new_data = data.reset_index(drop=True)
print(data)
"""
      语文  数学  英语  政治  体育
学生_0  74  98  74  40  94
学生_1  52  63  72  54  50
学生_2  54  90  92  70  61
学生_3  71  44  61  70  99
学生_4  88  79  57  64  95
学生_5  51  83  47  71  76
学生_6  70  42  48  90  44
学生_7  95  54  48  66  40
学生_8  55  65  59  94  65
学生_9  81  91  85  61  91
"""

print(new_data)
"""
   语文  数学  英语  政治  体育
0  74  98  74  40  94
1  52  63  72  54  50
2  54  90  92  70  61
3  71  44  61  70  99
4  88  79  57  64  95
5  51  83  47  71  76
6  70  42  48  90  44
7  95  54  48  66  40
8  55  65  59  94  65
9  81  91  85  61  91
"""

三、以某列值设置为新的索引

方法:set_index(keys, drop=True) -> DataFrame

  • 作用:设置新的下标索引
  • 参数说明:
    • keys:列索引名称或列索引名称的列表
    • drop:默认为False,即不删除原来的索引;如果为True,则删除原来的索引值
  • 注意:该方法返回一个DataFrame,对原来的DF并没有影响

示例:

# 1. 创建DF
df = pd.DataFrame({"month": [1, 4, 7, 10], 
                  "year": [2012, 2014, 2020, 2023],
                  "sale": [55, 40, 70, 35]})
print(df)
"""
   month  year  sale
0      1  2012    55
1      4  2014    40
2      7  2020    70
3     10  2023    35
"""


# 2. 将月份设置为索引
new_df = df.set_index("month")
print(new_df)
"""
       year  sale
month            
1      2012    55
4      2014    40
7      2020    70
10     2023    35
"""


# 3. 设置多个索引:以年和月份作为索引
new_df = df.set_index(["year", "month"])
print(new_df)
"""
            sale
year month      
2012 1        55
2014 4        40
2020 7        70
2023 10       35
"""

通过刚才的设置,这样DataFrame就变成了一个具有Multilndex的DataFrame。

4.3 MultiIndex

Multilndex是三维的数据结构,叫做多级索引。多级索引(也称层次化索引)是pandas的重要功能,可以在Series、DataFrame对象上拥有2个以及2个以上的索引。

4.3.1 MultiIndex的特性

打印刚才的df的行索引结果:

new_df.index

"""
MultiIndex([(2012,  1),
            (2014,  4),
            (2020,  7),
            (2023, 10)],
           names=['year', 'month'])
"""
print(new_df.index.names)  # ['year', 'month']
print(new_df.index.levels)  # [[2012, 2014, 2020, 2023], [1, 4, 7, 10]]
  • .index属性:
    • nameslevels的名称
    • levels:每个level的元组值

4.3.2 MultiIndex的创建

  1. from_tuples:使用元组列表创建 MultiIndex。
  2. from_arrays:使用数组列表创建 MultiIndex。
  3. from_product:使用笛卡尔积创建 MultiIndex。
  4. from_frame:从 DataFrame 创建 MultiIndex。

示例:

arrays = [[1, 1, 2, 2], ["red", "blue", "red", "blue"]]
pd.MultiIndex.from_arrays(arrays, names=("number", "color"))
"""
MultiIndex([(1,  'red'),
            (1, 'blue'),
            (2,  'red'),
            (2, 'blue')],
           names=['number', 'color'])
"""

import pandas as pd

# 1. `from_tuples`:使用元组列表创建 MultiIndex。
tup = ('X', 'A'), ('X', 'B'), ('Y', 'A'), ('Y', 'B')
index = pd.MultiIndex.from_tuples(tup)
print(index)
"""
MultiIndex([('X', 'A'),
            ('X', 'B'),
            ('Y', 'A'),
            ('Y', 'B')],
           )
"""


# 2. `from_arrays`:使用数组列表创建 MultiIndex。
arrays = ['X', 'X', 'Y', 'Y'], ['A', 'B', 'A', 'B']
index = pd.MultiIndex.from_arrays(arrays)
print(index)
"""
MultiIndex([('X', 'A'),
            ('X', 'B'),
            ('Y', 'A'),
            ('Y', 'B')],
           )
"""


# 3. `from_product`:使用笛卡尔积创建 MultiIndex。
cartesian_produc = [['X', 'Y'], ['A', 'B']]
index = pd.MultiIndex.from_product(cartesian_produc)
print(index)
"""
MultiIndex([('X', 'A'),
            ('X', 'B'),
            ('Y', 'A'),
            ('Y', 'B')],
           )
"""


# 4. `from_frame`:从 DataFrame 创建 MultiIndex。
dt = {'A': ['X', 'X', 'Y', 'Y'], 'B': ['A', 'B', 'A', 'B']}
df = pd.DataFrame(dt)
index = pd.MultiIndex.from_frame(df)
print(index)
"""
MultiIndex([('X', 'A'),
            ('X', 'B'),
            ('Y', 'A'),
            ('Y', 'B')],
           names=['A', 'B'])
"""

小结

  1. Pandas的优势【了解】
    • 增强图表可读性。
    • 便捷的数据处理能力。
    • 读取文件方便
    • 封装了Matplotlib、Numpy的画图和计算
  2. Series【知道】
    • 创建
      • pd.Series([], index=[])
      • pd.Series({})
    • 属性
      • 对象.index
      • 对象.values
  3. DataFrame【掌握】
    • 创建
      • pd.DataFrame(data=None, index=None, columns=None)
    • 属性
      • .shape – 形状
      • .index – 行索引
      • .columns – 列索引
      • .values – 查看值
      • .T – 转置
      • .head() – 查看头部内容
      • .tail() – 查看尾部内容
    • DataFrame索引
      • 修改的时候,需要进行全局修改
      • 对象.reset_index(drop=False) -> DF
      • 对象.set_index(keys, drop=False) -> DF
  4. Multilndex【了解】
    • 类似ndarray中的三维数组创建
    • 创建:
      • pd.Multilndex.from_arrays()
    • 属性:
      • 对象.index

4.4 索引操作

Numpy当中我们已经讲过使用索引选取序列和切片选择,pandas也支持类似的操作,也可以直接使用列名、行名称,甚至组合使用。

数据集下载地址:https://www.kaggle.com/datasets/varpit94/uber-stock-data

# 读取文件
data = pd.read_csv("./data/UBER.csv", index_col=0)
data = data.drop(["Adj Close", "Volume"], axis=1)  # 按列
print(data.head())

"""
                Open       High        Low      Close
Date                                                 
2019-5-10  42.000000  45.000000  41.060001  41.570000
2019-5-13  38.790001  39.240002  36.080002  37.099998
2019-5-14  38.310001  39.959999  36.849998  39.959999
2019-5-15  39.369999  41.880001  38.950001  41.290001
2019-5-16  41.480000  44.060001  41.250000  43.000000
"""

4.4.1 直接使用行列索引(先列后行)

示例:获取"2022-03-7’这天的’Close"的结果:

# 支持的操作 —— 先列后行
res = data["Close"]["2022-3-7"]
print(res)  # 28.57

# 不支持的操作
# res = data["2022-3-7"]["Close"]  # KeyError: '2022-02-27'

# 不支持的操作
# res = data[:1, :2]  # InvalidIndexError: (slice(None, 1, None), slice(None, 2, None))

与ndarray数组不同,上面的两种操作是不允许的!

4.4.2 结合loc或者iloc使用索引

  1. .loc(开始索引名, 结束索引名, 列名)
  2. .iloc[开始行索引: 结束行索引, 开始列索引: 结束列索引]

示例:获取从2021-2-22:2021-2-26,'open’的结果:

# 使用loc只能指定行列索引的名字
res = data.loc["2021-2-22": "2021-2-26", "Open"]
print(res)
"""
Date
2021-2-22    57.759998
2021-2-23    53.500000
2021-2-24    54.950001
2021-2-25    54.580002
2021-2-26    52.070000
Name: Open, dtype: float64
"""


# 使用iloc可以通过索引的下标去获取
res = data.iloc[:3, :5]  # 获取前3行,前5列的结果
print(res)
"""
                Open       High        Low      Close
Date                                                 
2019-5-10  42.000000  45.000000  41.060001  41.570000
2019-5-13  38.790001  39.240002  36.080002  37.099998
2019-5-14  38.310001  39.959999  36.849998  39.959999
"""

4.5 赋值操作

  1. df[列名] = 数值
  2. df.列名 = 数值

对DataFrame当中的Close列进行重新赋值为1。

# 直接修改原来的值
data["Close"] = 1
print(data.head())
"""
                Open       High        Low  Close
Date                                             
2019-5-10  42.000000  45.000000  41.060001      1
2019-5-13  38.790001  39.240002  36.080002      1
2019-5-14  38.310001  39.959999  36.849998      1
2019-5-15  39.369999  41.880001  38.950001      1
2019-5-16  41.480000  44.060001  41.250000      1
"""

# 或者
data.Close = 1
print(data.head())
"""
                Open       High        Low  Close
Date                                             
2019-5-10  42.000000  45.000000  41.060001      1
2019-5-13  38.790001  39.240002  36.080002      1
2019-5-14  38.310001  39.959999  36.849998      1
2019-5-15  39.369999  41.880001  38.950001      1
2019-5-16  41.480000  44.060001  41.250000      1
"""

4.6 排序

排序有两种形式:

  1. 对索引进行排序
  2. 对于内容进行排序

4.6.1 DataFrame排序

一、值排序

  • df.sort_values(axis=0, by=, ascending=True, inplace=False) -> df
  • 作用:单个键或者多个键进行排序
  • 参数:
    • axis:要排序的轴
    • by:指定排序参考的键
    • ascending:默认升序
      • ascending=False:降序
      • ascending=True:升序
    • inplace:原地排序(将结果赋值给原变量)

示例1:按照开盘价的大小进行升序排序。

data.sort_values(axis=0, by="Open", ascending=True, inplace=True)
print(data.head())
"""
            Open       High    Low      Close
Date                                         
2020-3-19  15.96  21.260000  15.70  20.490000
2020-3-18  17.76  17.799999  13.71  14.820000
2020-3-16  20.15  21.490000  19.10  20.290001
2020-3-17  20.18  20.309999  18.01  18.910000
2020-3-23  21.07  22.730000  19.73  22.400000
"""

示例2:按照多个键进行排序。

data.sort_values(by=["Open", "High"], ascending=True, inplace=True)
print(data)
"""
                Open       High        Low      Close
Date                                                 
2020-3-19  15.960000  21.260000  15.700000  20.490000
2020-3-18  17.760000  17.799999  13.710000  14.820000
2020-3-16  20.150000  21.490000  19.100000  20.290001
2020-3-17  20.180000  20.309999  18.010000  18.910000
2020-3-23  21.070000  22.730000  19.730000  22.400000
...              ...        ...        ...        ...
2021-3-15  60.349998  60.529999  59.119999  60.189999
2021-4-16  60.740002  60.849998  59.540001  60.349998
2021-2-16  61.020000  61.310001  59.840000  60.520000
2021-2-10  62.000000  63.500000  60.799999  63.180000
2021-2-11  63.250000  64.050003  60.395000  60.709999
"""

二、索引排序

  • df.sort_index(axis=0, ascending=True, inplace=False) -> df
  • 作用:对 DataFrame 的索引进行排序
  • 参数:
    • axis:要排序的轴
    • ascending:默认升序
      • ascending=False:降序
      • ascending=True:升序
    • inplace:原地排序(将结果赋值给原变量)

示例1:按照日期从大到小降序排序。

data.sort_index(axis=0, ascending=False, inplace=True)
print(data.head())
"""
               Open    High        Low      Close
Date                                             
2022-3-9  31.750000  32.730  31.200001  31.500000
2022-3-8  28.510000  31.570  28.278000  30.740000
2022-3-7  31.480000  31.938  28.549999  28.570000
2022-3-4  31.500000  31.730  29.270000  29.830000
2022-3-3  34.220001  34.291  31.415001  31.719999
"""

4.6.2 Series排序

因为Series只有一列,因此不需要额外的参数。

一、值排序

  • series.sort_values(axis=0, ascending=True, inplace=False) -> series
  • 作用:对 Series 的索引进行排序
  • 参数:
    • axis:要排序的轴
    • by:指定排序参考的键
    • ascending:默认升序
      • ascending=False:降序
      • ascending=True:升序
    • inplace:原地排序(将结果赋值给原变量)

示例:

series = data["Close"].copy()

series.sort_values(ascending=False, inplace=True)  # 对值进行降序
print(series.head())
"""
Date
2021-2-10    63.180000
2021-2-17    60.810001
2021-4-15    60.740002
2021-2-11    60.709999
2021-4-13    60.639999
Name: Close, dtype: float64
"""

二、索引排序

  • series.sort_index(axis=0, ascending=True, inplace=False) -> series
  • 作用:单个键或者多个键进行排序
  • 参数:
    • axis:要排序的轴
    • ascending:默认升序
      • ascending=False:降序
      • ascending=True:升序
    • inplace:原地排序(将结果赋值给原变量)
series.sort_index(ascending=True, inplace=True)  # 对索引进行升序
print(series.head())
"""
Date
2019-10-1     29.150000
2019-10-10    28.870001
2019-10-11    30.129999
2019-10-14    31.120001
2019-10-15    32.000000
Name: Close, dtype: float64
"""

小结

  1. 索引【掌握】
    • 直接索引 – 先列后行,是需要通过索引的字符串进行获取
    • loc – 先行后列,是需要通过索引的字符串进行获取
    • iloc – 先行后列,是通过下标进行索引
  2. 赋值【知道】
    • data["列名"]=数值
    • data.列名=数值
  3. 排序【知道】
    • dataframe
      • df.sort_values()
      • df.sort_index()
    • series
      • series.sort_values()
      • series.sort_index()

4.7 DataFrame运算

学习目标:

  1. 应用add等实现数据间的加、减法运算
  2. 应用逻辑运算符号实现数据的逻辑筛选
  3. 应用isinquery实现数据的筛选
  4. 使用describe完成综合统计
  5. 使用maxminmeanstd完成统计计算
  6. 使用idxminidxmax完成最大值最小值的索引
  7. 使用cumsum等实现累计分析
  8. 应用apply函数实现数据的自定义处理

4.7.1 算术运算

  1. add(other)
  2. sub(other)

直接使用加法运算符+和减法运算符-也是可以的,但一般不这样写。

一、add(other)

比如进行数学运算加上具体的一个数字。

res = data["Open"].add(1)
print(type(res))  # 
print(res.head())
"""
Date
2022-3-9    32.750000
2022-3-8    29.510000
2022-3-7    32.480000
2022-3-4    32.500000
2022-3-3    35.220001
Name: Open, dtype: float64
"""

# 直接使用 加法运算符+ 也是可以的
# 因为df[列名]返回的是一个view,因此不会改变原有的数据
res = data["Open"] + 1
print(type(res))  # 
print(res.head())
"""
Date
2022-3-9    32.750000
2022-3-8    29.510000
2022-3-7    32.480000
2022-3-4    32.500000
2022-3-3    35.220001
Name: Open, dtype: float64
"""

二、sub(other)

res = data["Open"].sub(1)
print(type(res))  # 
print(res.head())
"""
Date
2022-3-9    30.750000
2022-3-8    27.510000
2022-3-7    30.480000
2022-3-4    30.500000
2022-3-3    33.220001
Name: Open, dtype: float64
"""

# 直接使用 加法运算符+ 也是可以的
# 因为df[列名]返回的是一个view,因此不会改变原有的数据
res = data["Open"] - 1
print(type(res))  # 
print(res.head())
"""
Date
2022-3-9    30.750000
2022-3-8    27.510000
2022-3-7    30.480000
2022-3-4    30.500000
2022-3-3    33.220001
Name: Open, dtype: float64
"""

4.7.2 逻辑运算

一、逻辑运算符号

  1. >:返回逻辑结果(True/False)
  2. <:返回逻辑结果(True/False)
  3. >=:返回逻辑结果(True/False)
  4. <=:返回逻辑结果(True/False)
  5. &:与
  6. |:或

和ndarray对象的原理是一样的

示例:筛选data["Open"] > 30的日期数据:

condition = data["Open"] > 30
print(type(condition))  # 
print(condition.head())
"""
Date
2022-3-9     True
2022-3-8    False
2022-3-7     True
2022-3-4     True
2022-3-3     True
Name: Open, dtype: bool
"""


# 和ndarray对象一样,可以根据筛选结果取值
res = data[condition]
print(type(res))  # 
print(res.head())
"""
                Open       High        Low  Close
Date                                             
2022-3-9   31.750000  32.730000  31.200001      1
2022-3-7   31.480000  31.938000  28.549999      1
2022-3-4   31.500000  31.730000  29.270000      1
2022-3-3   34.220001  34.291000  31.415001      1
2022-3-24  34.740002  34.950001  33.439999      1
"""


# 完成多个逻辑判断
condition_1 = data["Open"] > 30
print(type(condition_1))  # 
condition_2 = data["High"] < 40
print(type(condition_2))  # 

res = data[condition_1 & condition_2]
print(type(res))  # 
print(res)
"""
                 Open       High        Low  Close
Date                                              
2022-3-9    31.750000  32.730000  31.200001      1
2022-3-7    31.480000  31.938000  28.549999      1
2022-3-4    31.500000  31.730000  29.270000      1
2022-3-3    34.220001  34.291000  31.415001      1
2022-3-24   34.740002  34.950001  33.439999      1
...               ...        ...        ...    ...
2019-10-17  31.799999  32.930000  31.450001      1
2019-10-16  31.799999  32.380001  31.438000      1
2019-10-15  31.200001  32.169998  31.195000      1
2019-10-14  30.219999  31.540001  29.819000      1
2019-10-1   30.370001  30.510000  28.650000      1

[278 rows x 4 columns]
"""

二、逻辑运算函数

  1. query(expr)
    • 作用:允许使用类似于 SQL 的语句来查询 DataFrame
    • 参数说明:
      • expr是一个字符串,表示用于过滤 DataFrame 的布尔表达式。
    • 例子:df.query("A < 4")。表示 A列中值小于 4 的行
  2. isin(values)
    • 作用: 用于过滤数据帧。isin()方法有助于选择在特定列中具有特定(或多个)值的行
    • 参数:
      • values是一个list,里面包含具体的值(这个列表并不是一个范围)
    • [A, B, C, D, E],这个list不是一个范围,而是特定的值

示例:

# 1. query(expr)
res = data.query("Open > 30 & Open < 50")
print(type(res))  # 
print(res.head())
"""
                Open       High        Low  Close
Date                                             
2022-3-9   31.750000  32.730000  31.200001      1
2022-3-7   31.480000  31.938000  28.549999      1
2022-3-4   31.500000  31.730000  29.270000      1
2022-3-3   34.220001  34.291000  31.415001      1
2022-3-24  34.740002  34.950001  33.439999      1
"""


# 2. isin(values)
res = data[data["Open"].isin([31, 32, 33, 34])]
print(type(res))  # 
print(res.head())
"""
           Open       High        Low  Close
Date                                        
2022-3-2   34.0  34.220001  32.970001      1
2020-8-28  33.0  33.939999  32.820000      1
2020-6-18  33.0  33.439999  32.799999      1
2020-6-16  34.0  34.169998  32.430000      1
2020-4-29  31.0  32.000000  30.330000      1
"""

4.7.3 统计运算

一、综合分析 —— .describe()

综合分析.describe()能够直接得出很多统计结果:

  • countmeanstdminmax

示例:

res = data.describe()
print(type(res))  # 
print(res)
"""
             Open        High         Low  Close
count  725.000000  725.000000  725.000000  725.0
mean    40.166447   40.961435   39.261123    1.0
std      9.198940    9.259164    9.075919    0.0
min     15.960000   17.799999   13.710000    1.0
25%     32.730000   33.419998   31.983000    1.0
50%     39.000000   39.959999   38.009998    1.0
75%     46.700001   47.520000   45.860001    1.0
max     63.250000   64.050003   60.799999    1.0
"""

说明:25%50%75%分别表示第一四分位数、第二四分位数(中位数)和第三四分位数。

  • 第一四分位数(25%):表示数据集中所有数值中有25%的数据比它小
  • 第二四分位数(50%):也就是中位数,表示数据集中所有数值的中间值。有50%的数据比它小,另外50%的数据比它大。
  • 第三四分位数(75%):表示数据集中所有数值中有75%的数据比它小

这些值可以帮助我们了解数据集的分布情况。

二、统计函数

Numpy当中已经详细介绍,在这里我们演示

  1. min(最小值)
  2. max(最大值)
  3. mean(平均值)
  4. median(中位数)
  5. var(方差)
  6. std(标准差)
  7. mode(众数)
统计方法 含义
min 返回DataFrame或Series中的最小值
max 返回DataFrame或Series中的最大值
mean 返回DataFrame或Series中的算术平均值
median 返回DataFrame或Series中的中位数
var 返回DataFrame或Series中的方差
std 返回DataFrame或Series中的标准差
mode 返回DataFrame或Series中的众数
abs 返回DataFrame或Series中每个元素的绝对值
prod 返回DataFrame或Series中所有元素的乘积
idxmax 返回DataFrame或Series中最大值的索引
idxmin 返回DataFrame或Series中最小值的索引

这些方法都是对 DataFrame 的每一列进行计算,返回一个 Series 对象,其中索引为原 DataFrame 的列名,值为对应列的计算结果(除了 abs 方法,它返回一个与原 DataFrame 形状相同的 DataFrame)。

说明:

  • mode众数:出现最多次数的数
  • median中位数:先将数据从小到大排列,再取最中间的那个数为中位数。如果没有中间数,取中间两个数的平均值。

对于单个函数去进行统计的时候,坐标轴还是按照默认列columns(axis=0, default),如果要对行index需要指定(axis=1)

示例:

# 使用统计函数时,0代表对列求结果;1代表对行求结果(默认为0)

df = pd.DataFrame({"COL1": [2, 3, 4, 5, 4, 2],
                   "COL2": [0, 1, 2, 3, 4, 2],
                   "COL3": [1, 2, 3, 4, 3, 1],
                  })


print("------------min------------")
# 1. min:返回DataFrame或Series中的最小值
res = df.min(0)
print(type(res))  # 
print(res, "\r\n")
"""
COL1    2
COL2    0
COL3    1
dtype: int64
"""

print("------------max------------")
# 2. max:返回DataFrame或Series中的最大值
res = df.max(0)
print(type(res))  # 
print(res, "\r\n")
"""
COL1    5
COL2    4
COL3    4
dtype: int64 
"""

print("------------mean------------")
# 3. mean:返回DataFrame或Series中的算术平均值
res = df.mean(0)
print(type(res))  # 
print(res, "\r\n")
"""
COL1    3.333333
COL2    2.000000
COL3    2.333333
dtype: float64 
"""

print("------------median------------")
# 4. median:返回DataFrame或Series中的中位数
res = df.median(0)
print(type(res))  # 
print(res, "\r\n")
"""
COL1    3.5
COL2    2.0
COL3    2.5
dtype: float64 
"""

print("------------var------------")
# 5. var:返回DataFrame或Series中的方差
res = df.var(0)
print(type(res))  # 
print(res, "\r\n")
"""
COL1    1.466667
COL2    2.000000
COL3    1.466667
dtype: float64 
"""

print("------------std------------")
# 6. std:返回DataFrame或Series中的标准差
res = df.std(0)
print(type(res))  # 
print(res, "\r\n")
"""
COL1    1.211060
COL2    1.414214
COL3    1.211060
dtype: float64 
"""

print("------------mode------------")
# 7. mode:返回DataFrame或Series中的众数
res = df.mode()
print(type(res))  # 
print(res, "\r\n")
"""
   COL1  COL2  COL3
0     2   2.0     1
1     4   NaN     3
"""

print("------------abs------------")
# 8. abs:返回DataFrame或Series中每个元素的绝对值
res = df.abs()
print(type(res))  # 
print(res, "\r\n")
"""
   COL1  COL2  COL3
0     2     0     1
1     3     1     2
2     4     2     3
3     5     3     4
4     4     4     3
5     2     2     1 
"""

print("------------prod------------")
# 9. prod:返回DataFrame或Series中所有元素的乘积
res = df.prod(0)
print(type(res))  # 
print(res, "\r\n")
"""
COL1    960
COL2      0
COL3     72
dtype: int64 
"""

print("------------idmax------------")
# 10. idxmax:返回DataFrame或Series中最大值的索引
res = df.idxmax(0)
print(type(res))  # 
print(res, "\r\n")
"""
COL1    3
COL2    4
COL3    3
dtype: int64 
"""

print("------------idmin------------")
# 11. idxmin:返回DataFrame或Series中最小值的索引
res = df.idxmin()
print(type(res))  # 
print(res, "\r\n")
"""
COL1    0
COL2    0
COL3    0
dtype: int64
"""

三、累积统计函数

在Pandas中,cumsumcummaxcummincumprod都是累积函数,它们分别用于计算累积和、累积最大值、累积最小值和累积乘积。

  1. cumsum(axis=0, skipna=True)
  2. cummax(axis=0, skipna=True)
  3. cummin(axis=0, skipna=True)
  4. cumprod(axis=0, skipna=True)
累积统计函数 作用
cumsum 计算前1/2/3/.../n个数的和
cummax 计算前1/2/3/.../n个数的最大值
cummin 计算前1/2/3/.../n个数的最小值
cumprod 计算前1/2/3/.../n个数的积

这四个函数都返回一个新的Series或DataFrame。

Q:那么如何让这些累积的结果更好的显示呢?
A:利用Matplotlib画图

语法示例:

import matplotlib.pyplot as plt  # 必须先导入matplotlib库

# plot显示图像
res = series.cumsum()
res.plot()  # 直接调用.plot()即可实现画图

# 需要调用show()才能显示图像
plt.show()

示例:

import matplotlib.pyplot as plt
from pylab import mpl
# 设置中文字体
mpl.rcParams["font.sans-serif"] = ["SimHei"]
# 设置正常显示符号
mpl.rcParams["axes.unicode_minus"] = False


print(data)
"""
                Open       High        Low      Close
Date                                                 
2019-5-10  42.000000  45.000000  41.060001  41.570000
2019-5-13  38.790001  39.240002  36.080002  37.099998
2019-5-14  38.310001  39.959999  36.849998  39.959999
2019-5-15  39.369999  41.880001  38.950001  41.290001
2019-5-16  41.480000  44.060001  41.250000  43.000000
...              ...        ...        ...        ...
2022-3-18  32.520000  33.419998  32.330002  33.360001
2022-3-21  32.820000  32.820000  31.250000  31.980000
2022-3-22  31.930000  33.599998  31.840000  33.349998
2022-3-23  32.709999  33.680000  32.570000  33.060001
2022-3-24  34.740002  34.950001  33.439999  34.700001

[725 rows x 4 columns]
"""

# 0.1 排序
data.sort_index(inplace=True)

# 0.2 获取Series
series_open = data["Open"]
print(series_open, "\r\n")
"""
Date
2019-10-1     30.370001
2019-10-10    29.209999
2019-10-11    28.950001
2019-10-14    30.219999
2019-10-15    31.200001
                ...    
2022-3-3      34.220001
2022-3-4      31.500000
2022-3-7      31.480000
2022-3-8      28.510000
2022-3-9      31.750000
Name: Open, Length: 725, dtype: float64
"""

# 1. cumsum(axis=0, skipna=True)
res = series_open.cumsum()
plt.figure(figsize=(20, 8))
plt.subplot(221)
res.plot()
plt.title("cumsum(axis=0, skipna=True)")
print(res, "\r\n")
"""
Date
2019-10-1        30.370001
2019-10-10       59.580000
2019-10-11       88.530001
2019-10-14      118.750000
2019-10-15      149.950001
                  ...     
2022-3-3      28997.434011
2022-3-4      29028.934011
2022-3-7      29060.414011
2022-3-8      29088.924011
2022-3-9      29120.674011
Name: Open, Length: 725, dtype: float64 
"""

# 2. cummax(axis=0, skipna=True)
res = series_open.cummax()
plt.subplot(222)
res.plot()
plt.title("cummax(axis=0, skipna=True)")
print(res, "\r\n")
"""
Date
2019-10-1     30.370001
2019-10-10    30.370001
2019-10-11    30.370001
2019-10-14    30.370001
2019-10-15    31.200001
                ...    
2022-3-3      63.250000
2022-3-4      63.250000
2022-3-7      63.250000
2022-3-8      63.250000
2022-3-9      63.250000
Name: Open, Length: 725, dtype: float64 
"""

# 3. cummin(axis=0, skipna=True)
res = series_open.cummin()
plt.subplot(223)
res.plot()
plt.title("cummin(axis=0, skipna=True)")
print(res, "\r\n")
"""
Date
2019-10-1     30.370001
2019-10-10    29.209999
2019-10-11    28.950001
2019-10-14    28.950001
2019-10-15    28.950001
                ...    
2022-3-3      15.960000
2022-3-4      15.960000
2022-3-7      15.960000
2022-3-8      15.960000
2022-3-9      15.960000
Name: Open, Length: 725, dtype: float64 
"""

# 4. cumprod(axis=0, skipna=True)
res = series_open.cumprod()
plt.subplot(224)
res.plot()
plt.title("cumprod(axis=0, skipna=True)")
plt.suptitle("累积统计函数")
print(res, "\r\n")
"""
Date
2019-10-1     3.037000e+01
2019-10-10    8.871077e+02
2019-10-11    2.568177e+04
2019-10-14    7.761030e+05
2019-10-15    2.421442e+07
                  ...     
2022-3-3               inf
2022-3-4               inf
2022-3-7               inf
2022-3-8               inf
2022-3-9               inf
Name: Open, Length: 725, dtype: float64 
"""

[学习笔记] 1. 机器学习前置知识_第21张图片

4.7.4 自定义运算

  • apply(func, axis=0)
    • 作用:用于将指定的函数应用于数据的轴(行或列)上
    • 参数说明:
      • func:要应用的函数。该函数应该接受一个Series作为输入,并返回一个标量或Series。
      • axis=0:指定要应用函数的轴。对于Series,该参数无效;对于DataFrame,可以设置为0或1(默认为0),分别表示沿行或沿列应用函数。在您提供的示例中,axis=0表示沿列应用函数。

示例:定义一个对列,最大值-最小值的函数

df = data[["Open", "Close"]].copy()
print(type(df))  # 
print(df.head())
"""
                 Open      Close
Date                            
2019-10-1   30.370001  29.150000
2019-10-10  29.209999  28.870001
2019-10-11  28.950001  30.129999
2019-10-14  30.219999  31.120001
2019-10-15  31.200001  32.000000
"""

res = df.apply(func=lambda x: x.max() - x.min(), axis=0)
print(type(res))  # 
print(res)
"""
Open     47.29
Close    48.36
dtype: float64
"""

# 我们看一下过程
process_1 = df.max()
print(type(process_1))  # 
print(process_1)
"""
Open     63.25
Close    63.18
dtype: float64
"""
process_2 = df.min()
print(type(process_2))  # 
print(process_2)
"""
Open     15.96
Close    14.82
dtype: float64
"""

小结

  • 算术运算【知道】
  • 逻辑运算【知道】
    • 逻辑运算符号
    • 逻辑运算函数
      • 对象.query()
      • 对象.isin()
  • 统计运算【知道】
    • 对象.describe()
    • 统计函数
    • 累积统计函数
  • 自定义运算【知道】
    • apply(func, axis=0)

4.8 Pandas画图

学习目标:

  • 了解DataFrame的画图函数
  • 了解Series的画图函数
  1. DataFrame.plot(kind="line")
  2. Series.plot(kind="line")
  • 作用:用于绘制 SeriesDataFrame 的函数。它使用由选项 plotting.backend 指定的后端。默认情况下,使用 matplotlib
  • 参数:
    • data: SeriesDataFrame 对象,调用该方法的对象。
    • x: 仅在数据为 DataFrame 时使用。标签或位置,默认为 None
    • y: 仅在数据为 DataFrame 时使用。标签、位置或标签列表、位置,默认为 None。允许绘制一列与另一列。
    • kind: 字符串,要生成的图形类型:
      • line:折线图(默认);
      • bar:垂直条形图;
      • barh:水平条形图;
      • hist:直方图;
      • box:箱线图;
      • kde:核密度估计图;
      • density:与 kde 相同;
      • area:面积图;
      • pie(x):饼图;
      • scatter(x, y):散点图(仅限 DataFrame);
      • hexbin(x, y):六边形图(仅限 DataFrame)。
    • ax: matplotlib 轴对象,默认为 None。当前图形的轴。
    • subplots: 布尔值或可迭代序列,默认为 False。是否将列分组到子图中。
    • sharex: 布尔值,默认为 True(如果 axNone)否则为 False。如果 subplots=True,则共享 x 轴并将某些 x 轴标签设置为不可见。
    • sharey: 布尔值,默认为 False。如果 subplots=True,则共享 y 轴并将某些 y 轴标签设置为不可见。
    • layout: 元组,可选(行,列),用于子图布局。
    • figsize: 元组(宽度,高度),以英寸为单位。图形对象的大小。
    • use_index: 布尔值,默认为 True。使用索引作为 x 轴刻度。
    • title: 字符串或列表。用于绘图的标题。如果传递了字符串,则在图形顶部打印字符串。如果传递了列表并且 subplotsTrue,则在相应子图上方打印列表中的每个项目。
    • grid: 布尔值,默认为 None(matlab 风格默认)。轴网格线。
    • legend: 布尔值或 {reverse}。在轴子图上放置图例。

需要注意的是

  1. DataFrame和Series都可以画图:
    1. 有些图的横坐标是index,数值为列;
    2. 有些图的横坐标需要我们指定;
    3. 有些图横纵坐标都需要我们指定。
  2. 因为DF可能有多列,数据比较多,所以我们可以使用df.locdf.iloc来截取部分数据,以方便展示!

示例:

import matplotlib.pyplot as plt


print(data.head())
"""
                 Open       High        Low      Close
Date                                                  
2019-10-1   30.370001  30.510000  28.650000  29.150000
2019-10-10  29.209999  29.280001  28.580000  28.870001
2019-10-11  28.950001  30.400000  28.940001  30.129999
2019-10-14  30.219999  31.540001  29.819000  31.120001
2019-10-15  31.200001  32.169998  31.195000  32.000000
"""
data_subset_df = data.iloc[:3, 1:2]  # 取前三行
print(type(data_subset_df))  # 
print(data_subset_df)
"""
                 High
Date                 
2019-10-1   30.510000
2019-10-10  29.280001
2019-10-11  30.400000
"""

data_subset_series = data.iloc[:3, 1]  # 取前三行
print(type(data_subset_series))  # 
print(data_subset_series)
"""
Date
2019-10-1     30.510000
2019-10-10    29.280001
2019-10-11    30.400000
Name: High, dtype: float64
"""

fig, axes = plt.subplots(4, 3, figsize=[40, 16])
data.plot(kind="line", ax=axes[0][0], title="kind=line", grid=True, legend=True)
data.iloc[:10, :].plot(kind="bar", ax=axes[0][1], title="kind=bar")
data.iloc[:10, :].plot(kind="barh", ax=axes[0][2], title="kind=barh")
data.plot(kind="hist", ax=axes[1][0], title="kind=hist")
data.plot(kind="box", ax=axes[1][1], title="kind=box")
data.plot(kind="kde", ax=axes[1][2], title="kind=kde")
data.plot(kind="density", ax=axes[2][0], title="kind=density")
data.plot(kind="area", ax=axes[2][1], title="kind=area")
data.iloc[:5, 2].plot(kind="pie", ax=axes[2][2], title="kind=pie")
data.plot(kind="scatter", x="Open", y="High", ax=axes[3][0], title="kind=scatter")
data.plot(kind="hexbin", x="Open", y="High", ax=axes[3][1], title="kind=hexbin")

# 不能直接调节透明度,我们可以对axes进行调节
# 使用 for 循环遍历所有轴对象并调整网格线透明度
# for row in axes:
#     for ax in row:
#         ax.grid(True, alpha=0.5)
        
plt.show()

[学习笔记] 1. 机器学习前置知识_第22张图片

4.9 文件读取与存储

学习目标

  • 了解Pandas的几种文件读取存储操作
  • 应用csv方式、HDF方式和json方式实现文件的读取和存储

我们的数据大部分存在于文件当中,所以pandas会支持复杂的I/O操作,pandas的API支持众多的文件格式,如csv、sql、xls、json、HDF5。

注:最常用的HDF5和CSV文件

|文件格式|数据描述|文件后缀|读取语法|写入语法|
|-|-|-|-|-|-|
|text|CSV|.csv|pd.read_csv()|df.to_csv()|
|text|JSON|.json|pd.read_json()|df.to_json()|
|text|HTML|.html.htm|pd.read_html()|df.to_html()|
|text|Local clipboard(剪切板)|无|pd.read_clipboard()|df.to_clipboard()|
|binary|MS Excel|.xls.xlsx|pd.read_excel()|df.to_excel()|
|binary|HDF5 Format|.h5.hdf5|pd.read_hdf()|df.to_hdf()|
|binary|Feather Format|.feather|pd.read_feather()|df.to_feather()|
|binary|Parquet Format|.parquet|pd.read_parquet()|df.to_parquet()|
|binary|Msgpack|.msg.mspack|pd.read_msgpack()|df.to_msgpack()|
|binary|Stata|.dta|pd.read_stata()|df.to_stata()|
|binary|SAS|.sas7bdat|pd.read_sas()|``|
|binary|Python Pickle Format|.pkl.pickle|pd.read_pickle()|df.to_pickle()|
|SQL|SQL|无(.sql.db)|pd.read_sql()|pd.to_sql()|
|SQL|Google Big Query|无|pd.read_gbq()|df.to_gbq()|

注意:

  • Local clipboard 不是一种文件格式,而是指计算机的剪贴板。它不具有特定的文件后缀。
  • SQL通常没有特定的文件后缀,但有时会使用 .sql.db
  • Google Big Query:不是一种文件格式,而是一种云端数据存储和分析服务,因此没有特定的文件后缀。

文本文件和二进制文件的优缺点:

  1. 二进制文件的优点包括:
    • 通常占用更少的磁盘空间,因为它们可以使用更紧凑的数据表示形式。
    • 可以更快地读取和写入,因为不需要进行文本解析和格式化。
    • 可以存储更多类型的数据,包括图像、音频和视频等。
  2. 二进制文件的缺点包括:
    • 不易于人类阅读和编辑,因为它们不是以文本形式存储的。
    • 可能不具有跨平台兼容性,因为不同的计算机系统可能使用不同的二进制数据表示形式。
  3. 文本文件的优点包括:
    • 易于人类阅读和编辑,因为它们是以文本形式存储的。
    • 具有很好的跨平台兼容性,因为文本文件通常使用标准化的字符编码。
  4. 文本文件的缺点包括:
    • 通常占用更多的磁盘空间,因为它们使用文本形式存储数据。
    • 读取和写入速度可能较慢,因为需要进行文本解析和格式化。
    • 可能无法存储某些类型的数据,例如图像、音频和视频等。

4.9.1 CSV

CSV(Comma-Separated Values) 是逗号分隔值的意思。CSV 文件是一个存储表格和电子表格信息的纯文本文件,其内容通常是一个文本、数字或日期的表格。CSV 文件可以使用以表格形式存储数据的程序轻松导入和导出。通常 CSV 文件的第一行包含表格的列标签。随后的每一行代表表格的一行。逗号分隔行中的每个单元格,这就是名称的由来。

  1. pd.read_csv(filepath_or_buffer, sep=",") -> DataFrame
  2. df.to_csv()

一、pd.read_csv()

pd.read_csv(filepath_or_buffer, sep=",") -> DataFrame

  • 作用:用于从 CSV 文件(逗号分隔值)或类似的文本文件中读取数据。它返回一个包含文件数据的 pandas.DataFrame 对象。
  • 参数说明:
    • filepath_or_buffer:字符串或文件句柄,指定要读取的文件的路径或类似文件的对象。
    • sep:字符串,指定字段分隔符。
      • 默认为 ,
    • header:整数或整数列表,指定行号以用作列名。
      • 默认为 infer,表示第一行为列名。
    • names:数组类型,指定列名。
      • 如果文件中不包含列名,则应指定此参数。
    • index_col:整数、字符串或整数/字符串序列,指定一列或多列作为 DataFrame 的行索引。
      • 如果不想进行索引,可以设置index_col=None
    • usecols:列表类型,指定要读取的列。可以使用列索引或列名。
  • 返回值:
    • 返回值是一个 pandas.DataFrame 对象,其中包含从 CSV 文件中读取的数据。

示例:读取UBER.csv文件,并指定只获取"Open", "Close"指标

# 读取csv文件,并指定只获取"Open", "Close"指标
data = pd.read_csv(filepath_or_buffer="./data/UBER.csv", sep=',', index_col="Date", usecols=["Date", "Open", "Close"])
print(data.head())
"""
                Open      Close
Date                           
2019-5-10  42.000000  41.570000
2019-5-13  38.790001  37.099998
2019-5-14  38.310001  39.959999
2019-5-15  39.369999  41.290001
2019-5-16  41.480000  43.000000
"""

二、df.to_csv()

df.to_csv(path_or_buf, sep=",") -> None

  • 作用:用于将 pandas.DataFrame 对象中的数据写入 CSV 文件(逗号分隔值)或类似的文本文件。它返回 None。
  • 参数说明:
    • path_or_buf:字符串或文件句柄,指定要写入的文件的路径或类似文件的对象。
    • sep:字符串,指定字段分隔符。
      • 默认为 ,
    • na_rep:字符串,指定缺失值的表示形式。
      • 默认为 ''(空字符串:什么都不写入)。
    • header:布尔值或字符串列表,指定是否写入列名。
      • 如果为 True,则使用列名;
      • 如果为字符串列表,则使用提供的列名;
      • 如果为 False,则不写入列名。
    • index:布尔值,指定是否写入行索引。默认为 True
    • columns:序列类型,指定要写入的列。
      • 如果未指定,则写入所有列。
    • mode:指定写入文件时使用的模式。它的默认值为 'w',表示写入模式。
      • 'w':写入模式。如果文件已存在,则覆盖其内容;如果文件不存在,则创建新文件。
      • 'a':追加模式。如果文件已存在,则在其末尾追加内容;如果文件不存在,则创建新文件。
      • 'x':独占模式。仅当文件不存在时才创建新文件。
  • 返回值:
    • 没有返回值

示例:保存"Open"列的数据,并读取查看结果。

# 保存"Open"列的数据,并读取查看结果。
data.to_csv(path_or_buf="./data/UBER_Open.csv", header=True, index=True, columns=["Open"], mode='w')
print("保存成功!")

# 读取
res = pd.read_csv(filepath_or_buffer="./data/UBER_Open.csv")
print(res.head())
"""
        Date       Open
0  2019-5-10  42.000000
1  2019-5-13  38.790001
2  2019-5-14  38.310001
3  2019-5-15  39.369999
4  2019-5-16  41.480000
"""

# 重新确定索引(在读取csv文件的时候也可以进行)
res.set_index(keys="Date", drop=True, inplace=True)
print(res.head())
"""
                Open
Date                
2019-5-10  42.000000
2019-5-13  38.790001
2019-5-14  38.310001
2019-5-15  39.369999
2019-5-16  41.480000
"""

根据上面的结果会发现将索引存入到文件当中,变成单独的一列数据。如果需要删除,可以指定index参数,删除原来的文件,重新保存一次。

# 保存"Open"列的数据,并读取查看结果。
data.to_csv(path_or_buf="./data/UBER_Open.csv", header=True, index=False, columns=["Open"], mode='w')
print("保存成功!")

# 读取
res = pd.read_csv(filepath_or_buffer="./data/UBER_Open.csv", index_col=None)
print(res.head())
"""
        Open
0  42.000000
1  38.790001
2  38.310001
3  39.369999
4  41.480000
"""

"""
在代码中将 `index_col` 参数设置为 `None`,这意味着在读取数据时不会使用任何一列作为索引。
但是,当我们查看结果时,我们会发现仍然显示了索引。这是因为在 Pandas 中,每个 DataFrame 
都有一个默认的索引,即使我们没有指定索引列。这个默认的索引是一个整数序列,从 0 开始。

所以,在我们的例子中,看到的索引实际上是 DataFrame 的默认索引,而不是从文件中读取的数据。
"""

4.9.2 HDF5

HDF5文件的英文全称是Hierarchical Data Format Version 5。它是一种存储相同类型数值的大数组的机制,适用于可被层次性组织且数据集需要被元数据标记的数据模型。一个HDF5文件是一种存放两类对象的容器:dataset和group。Dataset是类似于数组的数据集,而group是类似文件夹一样的容器,存放dataset和其他group。

  1. pd.read_hdf()
  2. df.to_hdf()

HDF5文件的读取和存储需要指定一个键,值为要存储的DataFrame。

一、pd.read_hdf()

pd.read_hdf(path_or_buf, key=None, **kwargs) -> DataFrame

  • 作用:用于从 HDF5 文件中读取数据。HDF5 是一种用于存储大量数据的文件格式。
  • 参数说明:
    • path_or_buf:指定要读取的 HDF5 文件的路径。
    • key:指定要读取的 HDF5 文件中的对象。
    • mode:指定文件打开模式,默认为 'r',表示只读模式。
      • 'r':只读模式。这是默认值。
      • 'r+':读写模式。文件必须已经存在。
      • 'a':读写模式。如果文件不存在,则创建新文件。
      • 'w''w-':写模式。如果文件已经存在,则覆盖原有内容。
      • 需要注意的是,mode 参数只在使用 PyTables 库时有效。如果使用的是 h5py 库,则 mode 参数会被忽略。
    • where:指定查询条件,用于筛选数据。
    • columns:指定要读取的列。
    • startstop:指定要读取的行范围。
  • 返回值:
    • 返回一个 DataFrame 对象,其中包含从 HDF5 文件中读取的数据。

示例:

# 读取.h5文件
data = pd.read_hdf(path_or_buf="./data/UBER.h5", key="uber")
print(type(data))  # 
print(data.head())
"""
                Open       High        Low      Close  Adj Close     Volume
Date                                                                       
2019-5-10  42.000000  45.000000  41.060001  41.570000  41.570000  186322500
2019-5-13  38.790001  39.240002  36.080002  37.099998  37.099998   79442400
2019-5-14  38.310001  39.959999  36.849998  39.959999  39.959999   46661100
2019-5-15  39.369999  41.880001  38.950001  41.290001  41.290001   36086100
2019-5-16  41.480000  44.060001  41.250000  43.000000  43.000000   38115500
"""

注意:在读取.h5文件时报错,需要安装tables库。

pip install tables

二、df.to_hdf()

df.to_hdf(path_or_buf, key, **kwargs) -> None

  • 作用:用于将 pandas DataFrame 写入 HDF5 文件的方法。HDF5 是一种自描述的文件格式,允许应用程序在没有外部信息的情况下解释文件的结构和内容。一个 HDF5 文件可以容纳一组相关对象,可以作为一组或作为单个对象访问。它返回 None。
  • 参数说明:
    • path_or_buf:文件路径或 HDFStore 对象。
    • key:存储中组的标识符。
    • mode:打开文件的模式。默认为 ‘a’,表示追加模式。
      • 'w':写入模式。如果文件已存在,则覆盖其内容;如果文件不存在,则创建新文件。
      • 'a':追加模式。如果文件已存在,则在其末尾追加内容;如果文件不存在,则创建新文件。
      • 'x':独占模式。仅当文件不存在时才创建新文件。
    • format:指定写入格式。可选值为 ‘fixed’ 或 ‘table’。
    • data_columns:要创建为磁盘查询的索引数据列的列列表,或 True 以使用所有列。
  • 返回值:
    • 没有返回值

示例:

# 存储文件
data.iloc[:10, :].to_hdf(path_or_buf="./data/UBER_row_10.h5", key="row_10", mode='w')
print("保存成功")

# 再次读取文件并查看内容
res = pd.read_hdf("./data/UBER_row_10.h5", key="row_10")
print(type(res))  # 
print(res)
"""
                Open       High        Low      Close  Adj Close     Volume
Date                                                                       
2019-5-10  42.000000  45.000000  41.060001  41.570000  41.570000  186322500
2019-5-13  38.790001  39.240002  36.080002  37.099998  37.099998   79442400
2019-5-14  38.310001  39.959999  36.849998  39.959999  39.959999   46661100
2019-5-15  39.369999  41.880001  38.950001  41.290001  41.290001   36086100
2019-5-16  41.480000  44.060001  41.250000  43.000000  43.000000   38115500
2019-5-17  41.980000  43.290001  41.270000  41.910000  41.910000   20225700
2019-5-20  41.189999  41.680000  39.459999  41.590000  41.590000   29222300
2019-5-21  42.000000  42.240002  41.250000  41.500000  41.500000   10802900
2019-5-22  41.049999  41.279999  40.500000  41.250000  41.250000    9089500
2019-5-23  40.799999  41.090000  40.020000  40.470001  40.470001   11119900
"""

三、key参数及其获取

key 参数用于指定存储中组的标识符。你可以任意指定 key 的值,但是要确保它是一个字符串。如果你想在同一个 HDF5 文件中添加另一个 DataFrame 或 Series,请使用追加模式并使用不同的键。

Q1:这个key相当于是一个密码吗?
A1:不,key 参数不是密码。它是一个字符串,用于标识 HDF5 文件中的组。你可以把它看作是一个名称,用于在 HDF5 文件中组织和存储数据。

Q2:那如果我不知道一个.h5文件的key,那么在使用pd.read_hdf()时怎么确定key参数呢?
A2:如果你不知道一个 HDF5 文件中的 key,你可以使用 pandas.HDFStore 类来查看文件中可用的键。例如,你可以这样做:

store = pd.HDFStore(path="./data/UBER.h5")
print(store.keys())  # ['/uber']
store.close()

上面的代码将打开名为 filename.h5 的 HDF5 文件,并打印出文件中可用的键。然后你就可以使用这些键中的一个来读取数据了。

四、h5文件的优势

推荐优先选择使用HDF5文件存储,原因如下:

  1. HDF5在存储的时候支持压缩,使用的方式是blosc,这个是速度最快的也是pandas默认支持的
  2. 使用压缩可以提磁盘利用率,节省空间
  3. HDF5还是跨平台的,它可以在不同的操作系统和硬件平台上使用,包括 PC 端和移动端、Windows 和 Linux、安卓和 iOS 等

4.9.3 JSON

JSON 是一种轻量级的数据交换格式,它基于 ECMAScript (w3c制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。它易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。

JSON 数据的书写格式是:名称/值对。名称/值对组合中的名称写在前面(在双引号中),值对写在后面,中间用冒号隔开。其中值可以是:数字(整数或浮点数)、字符串(在双引号中)、布尔值(true或false)、数组(在方括号中)、对象(在花括号中)、null。

  1. pd.read_json()
  2. df.to_json()

一、pd.read_json()

pd.read_json(path_or_buf=None, orient=None, typ="frame", lines=False) -> DataFrame

  • 作用:将 JSON 格式的字符串或文件读取为 pandas DataFrame。
  • 参数说明:
    • path_or_buf:默认为 None。表示要读取的 JSON 字符串或文件路径。
    • orient:str,指定解析 JSON 的格式。可选值包括
      • ‘split’:表示 JSON 字符串为 dict-like {index -> [index], columns -> [columns], data -> [values]} 的形式。
        • {index -> [index], columns -> [columns], data -> [values]} 这样的字典
      • ‘records’:表示 JSON 字符串为 list-like [{column -> value}, … , {column -> value}] 的形式。
        • [ {column -> value}, ... , {column -> value}] 这样的列表
      • ‘index’:表示 JSON 字符串为 dict-like {index -> {column -> value}} 的形式。
        • {index -> {column -> value}} 这样的字典
      • ‘columns’: 表示 JSON 字符串为 dict-like {column -> {index -> value}} 的形式。
        • {column -> {index -> value}} 这样的字典
      • ‘values’:表示 JSON 字符串仅为 values 数组。
        • 只是值数组
    • typ:str,指定返回的数据类型,默认为 ‘frame’。
      • 可选值包括:
        • ‘frame’:表示返回一个 pandas DataFrame 对象。这是默认值。
        • ‘series’:表示返回一个 pandas Series 对象。
    • dtype:参数用于控制是否推断列的数据类型。它可以是布尔值或字典。默认为 True。
      • 如果 dtype 为 True(默认值),则会推断列的数据类型。
      • 如果 dtype 为 False,则不会推断列的数据类型,而是直接使用数据。
      • 如果 dtype 为字典,则会使用字典中指定的列的数据类型。
    • lines:bool,默认为 False。
      • 如果为 True,则每行读取该文件作为 json 对象。
  • 返回值:
    • 返回一个 DataFrame 对象。

直观展示orient参数

下面是一个示例 DataFrame:

   A  B
0  1  2
1  3  4
  1. orient='split' 时,JSON 字符串如下:

‘split’:表示 JSON 字符串为 dict-like {index -> [index], columns -> [columns], data -> [values]} 的形式。

{"columns":["A","B"],"index":[0,1],"data":[[1,2],[3,4]]}

对于 orient='split',JSON 字符串中的键名 "columns", "index", "data" 是固定的,不能更改。

  1. orient='records' 时,JSON 字符串如下:

‘records’:表示 JSON 字符串为 list-like [{column -> value}, … , {column -> value}] 的形式。

[{"A":1,"B":2},{"A":3,"B":4}]
  1. orient='index' 时,JSON 字符串如下:

‘index’:表示 JSON 字符串为 dict-like {index -> {column -> value}} 的形式。

{"0":{"A":1,"B":2},"1":{"A":3,"B":4}}
  1. orient='columns' 时,JSON 字符串如下:

‘columns’: 表示 JSON 字符串为 dict-like {column -> {index -> value}} 的形式。

{"A":{"0":1,"1":3},"B":{"0":2,"1":4}}
  1. orient='values' 时,JSON 字符串如下:

‘values’:表示 JSON 字符串仅为 values 数组。

[[1,2],[3,4]]

直观展示lines参数

下面是一个简单的例子,演示如何使用 lines 参数从包含多个 JSON 对象的文件中读取数据:

import pandas as pd
from io import StringIO

data = """
{"a": 1, "b": 2}
{"a": 3, "b": 4}
"""

df = pd.read_json(StringIO(data), lines=True)
print(df)

输出结果为:

   a  b
0  1  2
1  3  4

在这个例子中,我们使用 StringIO 模拟一个包含多行 JSON 对象的文件。然后,我们使用 pd.read_json() 函数并将 lines 参数设置为 True 来读取数据。最后,我们打印出结果 DataFrame。

lines 参数设置为 False(默认值)时,pd.read_json() 函数期望读取一个包含单个 JSON 对象的文件。下面是一个简单的例子,演示如何从包含单个 JSON 对象的文件中读取数据:

import pandas as pd
from io import StringIO

data = """
{
    "a": [1, 3],
    "b": [2, 4]
}
"""

df = pd.read_json(StringIO(data))
print(df)

输出结果为:

   a  b
0  1  2
1  3  4

在这个例子中,我们使用 StringIO 模拟一个包含单个 JSON 对象的文件。然后,我们使用 pd.read_json() 函数并保留 lines 参数的默认值 False 来读取数据。最后,我们打印出结果 DataFrame。


示例:

这里使用一个新闻标题讽刺数据集,格式为json。“is_sarcastic”: 1讽刺的,否则为0;“headline”:新闻报道的标题;“article_link”:链接到原始新闻文章。存储格式为:

案例数据集下载地址:https://www.kaggle.com/datasets/rmisra/news-headlines-dataset-for-sarcasm-detection

{"article_link": "https://www.huffingtonpost.com/entry/versace-black-code_us_5861fbefe4b0de3a08f600d5", "headline": "former versace store clerk sues over secret 'black code' for minority shoppers", "is_sarcastic": 0}
{"article_link": "https://www.huffingtonpost.com/entry/roseanne-revival-review_us_5ab3a497e4b054d118e04365", "headline": "the 'roseanne' revival catches up to our thorny political mood, for better and worse", "is_sarcastic": 0}

读取:orient指定解析 JSON 的格式。

import pandas as pd

# lines = True
df_json = pd.read_json("./data/Sarcasm_Headlines_Dataset.json", orient="records", lines=True)
print(type(df_json))  # 
print(df_json.head())
"""
                                        article_link  \
0  https://www.huffingtonpost.com/entry/versace-b...   
1  https://www.huffingtonpost.com/entry/roseanne-...   
2  https://local.theonion.com/mom-starting-to-fea...   
3  https://politics.theonion.com/boehner-just-wan...   
4  https://www.huffingtonpost.com/entry/jk-rowlin...   

                                            headline  is_sarcastic  
0  former versace store clerk sues over secret 'b...             0  
1  the 'roseanne' revival catches up to our thorn...             0  
2  mom starting to fear son's web series closest ...             1  
3  boehner just wants wife to listen, not come up...             1  
4  j.k. rowling wishes snape happy birthday in th...             0  
"""

# lines = False
# df_json = pd.read_json("./data/Sarcasm_Headlines_Dataset.json", orient="records", lines=False)
# print(df_json)  # ValueError: Trailing data
"""
ValueError: Trailing data 是一个常见的错误,它通常发生在使用 pd.read_json() 函数读取 JSON 文件时。这个错误表示 JSON 文件中存在多余的数据。

这个错误通常发生在 JSON 文件中包含多个 JSON 对象,但没有使用 lines=True 参数来指定每行都是一个单独的 JSON 对象。例如,如果你有一个 JSON 文件,其中包含多个 JSON 对象,每个对象都在一行中,你可以使用 pd.read_json(file, lines=True) 来读取这个文件。

如果你的 JSON 文件不是这种格式,那么你可能需要检查文件中是否存在多余的数据,并删除它们。
"""


# 1. orient = split
# df_json = pd.read_json("./data/Sarcasm_Headlines_Dataset.json", orient="split", lines=True)
# print(df_json.head())  # AttributeError: 'list' object has no attribute 'items'


# 2. orient = records
df_json = pd.read_json("./data/Sarcasm_Headlines_Dataset.json", orient="records", lines=True)
print(df_json.head())
"""
                                        article_link  \
0  https://www.huffingtonpost.com/entry/versace-b...   
1  https://www.huffingtonpost.com/entry/roseanne-...   
2  https://local.theonion.com/mom-starting-to-fea...   
3  https://politics.theonion.com/boehner-just-wan...   
4  https://www.huffingtonpost.com/entry/jk-rowlin...   

                                            headline  is_sarcastic  
0  former versace store clerk sues over secret 'b...             0  
1  the 'roseanne' revival catches up to our thorn...             0  
2  mom starting to fear son's web series closest ...             1  
3  boehner just wants wife to listen, not come up...             1  
4  j.k. rowling wishes snape happy birthday in th...             0 
"""


# 3. orient = index
# df_json = pd.read_json("./data/Sarcasm_Headlines_Dataset.json", orient="index", lines=True)
# print(df_json.head())  # AttributeError: 'list' object has no attribute 'values'


# 4. orient = columns
df_json = pd.read_json("./data/Sarcasm_Headlines_Dataset.json", orient="columns", lines=True)
print(df_json.head())
"""
                                        article_link  \
0  https://www.huffingtonpost.com/entry/versace-b...   
1  https://www.huffingtonpost.com/entry/roseanne-...   
2  https://local.theonion.com/mom-starting-to-fea...   
3  https://politics.theonion.com/boehner-just-wan...   
4  https://www.huffingtonpost.com/entry/jk-rowlin...   

                                            headline  is_sarcastic  
0  former versace store clerk sues over secret 'b...             0  
1  the 'roseanne' revival catches up to our thorn...             0  
2  mom starting to fear son's web series closest ...             1  
3  boehner just wants wife to listen, not come up...             1  
4  j.k. rowling wishes snape happy birthday in th...             0 
"""


# 5. orient = values
df_json = pd.read_json("./data/Sarcasm_Headlines_Dataset.json", orient="values", lines=True)
print(df_json.head())
"""
                                        article_link  \
0  https://www.huffingtonpost.com/entry/versace-b...   
1  https://www.huffingtonpost.com/entry/roseanne-...   
2  https://local.theonion.com/mom-starting-to-fea...   
3  https://politics.theonion.com/boehner-just-wan...   
4  https://www.huffingtonpost.com/entry/jk-rowlin...   

                                            headline  is_sarcastic  
0  former versace store clerk sues over secret 'b...             0  
1  the 'roseanne' revival catches up to our thorn...             0  
2  mom starting to fear son's web series closest ...             1  
3  boehner just wants wife to listen, not come up...             1  
4  j.k. rowling wishes snape happy birthday in th...             0 
"""

[学习笔记] 1. 机器学习前置知识_第23张图片


二、df.to_json()

df.to_json(path_or_buf=None, orient=None, lines=False) -> None

  • 作用:将 Pandas DataFrame 对象转换为 JSON 字符串的函数。注意,NaN 和 None 会被转换为 null,而 datetime 对象会被转换为 UNIX 时间戳。它返回 None。
  • 参数说明:
    • path_or_buf:字符串、路径对象或类文件对象,或 None(默认)。
      • 如果为 None,则结果作为字符串返回。
    • orient:指示预期的 JSON 字符串格式。对于 DataFrame,默认值为 ‘columns’,允许的值有:{"split""records""index""columns""values""table"}。
    • date_format:日期转换类型。它可以是 None'epoch''iso'
      • 如果设置为 'epoch',则日期将转换为 epoch 毫秒;
      • 如果设置为 'iso',则日期将转换为 ISO8601 格式。
      • 默认值取决于 orient 参数。
        • 对于 orient='table',默认值为 'iso'
        • 对于所有其他 orient,默认值为 'epoch'
    • double_precision:编码浮点值时使用的小数位数,默认为 10。
    • force_ascii:强制编码字符串为 ASCII,默认为 True。
    • date_unit:编码时间单位,默认为 “ms”(毫秒)。
    • default_handler:如果对象无法转换为 JSON 的适当格式,则调用的处理程序,默认为 None。
    • lines:如果 “orient” 为 “records”,则以行分隔的 json 格式写出,默认为 False。
    • compression:用于对输出数据进行即时压缩。
      • 如果设置为 'infer'(默认值),并且 path_or_buf 是类路径,则从以下扩展名中检测压缩:‘.gz’、‘.bz2’、‘.zip’、‘.xz’、‘.zst’、‘.tar’、‘.tar.gz’、‘.tar.xz’ 或 ‘.tar.bz2’(否则不压缩)。
      • 如果设置为 None,则不进行压缩。
  • 返回值:
    • 没有返回值

示例:

# 1. 存储文件(lines=False)
df_json.iloc[:3, :].to_json("./data/test.json", orient="records")

"""
此时Json文件中的内容如下:

[{"article_link":"https:\/\/www.huffingtonpost.com\/entry\/versace-black-code_us_5861fbefe4b0de3a08f600d5","headline":"former versace store clerk sues over secret 'black code' for minority shoppers","is_sarcastic":0},{"article_link":"https:\/\/www.huffingtonpost.com\/entry\/roseanne-revival-review_us_5ab3a497e4b054d118e04365","headline":"the 'roseanne' revival catches up to our thorny political mood, for better and worse","is_sarcastic":0},{"article_link":"https:\/\/local.theonion.com\/mom-starting-to-fear-son-s-web-series-closest-thing-she-1819576697","headline":"mom starting to fear son's web series closest thing she will have to grandchild","is_sarcastic":1}]

因为我们没有设置lines=True,所以所有内容都保存到一行了
"""

# 2. 存储文件(lines=True):为了方便我们查看JSON文件,设置lines=True
df_json.iloc[:3, :].to_json("./data/test.json", orient="records", lines=True)

"""
此时Json文件中的内容如下:

{"article_link":"https:\/\/www.huffingtonpost.com\/entry\/versace-black-code_us_5861fbefe4b0de3a08f600d5","headline":"former versace store clerk sues over secret 'black code' for minority shoppers","is_sarcastic":0}
{"article_link":"https:\/\/www.huffingtonpost.com\/entry\/roseanne-revival-review_us_5ab3a497e4b054d118e04365","headline":"the 'roseanne' revival catches up to our thorny political mood, for better and worse","is_sarcastic":0}
{"article_link":"https:\/\/local.theonion.com\/mom-starting-to-fear-son-s-web-series-closest-thing-she-1819576697","headline":"mom starting to fear son's web series closest thing she will have to grandchild","is_sarcastic":1}
"""

4.10 高级处理:缺失值处理

学习目标:

  1. 应用isnull判断是否有缺失数据NaN。
  2. 应用fillna实现缺失值的填充
  3. 应用dropna实现缺失值的删除
  4. 应用replace实现数据的替换

在Pandas中,缺失值分为两种,一种是Pandas中的空值,另一种是自定义的缺失值。Pandas中的空值有三个:np.nan (Not a Number)Nonepd.NaT (时间格式的空值,注意大小写不能错),这三个值可以用Pandas中的函数isnull (),notnull (),isna ()进行判断。

  • NaN 是 “Not a Number” 的缩写,中文翻译为 “非数字”。它表示一个未定义或不可表示的值,通常用于浮点运算中。
  • null 是一个特殊的值,表示没有值或没有对象。它在许多编程语言中都有类似的概念。在中文中,它通常被翻译为 “空值” 或 “无”。
  • None 是 Python 中的一个特殊常量,表示空值或无。它在许多情况下用于表示变量未被初始化或函数没有返回值。在中文中,它通常被翻译为 “无” 或 “空”。

在Pandas中,当你使用 read_csv() 函数读取一个CSV文件时,函数会自动将空值(例如空字符串或空单元格)转换为 np.nan


如何处理nan

  • 获取缺失值的标记方式(NaN或者其他标记方式)
  • 如果缺失值的标记方式是NaN
    • 判断数据中是否包含NaN:
      • pd.isnull(df) -> df:接受一个参数,即要检测的对象(例如DataFrame或Series),并返回一个与输入对象形状相同的布尔值对象,其中缺失值的位置为True,非缺失值的位置为False。
      • pd.notnull(df) -> df:与 pd.isnull(df) 函数相反,它返回一个与输入对象形状相同的布尔值对象,其中缺失值的位置为False,非缺失值的位置为True。
      • 返回一个值为bool的df对象一般需要借助numpy函数来判断这个对象是否存在缺失值:
        • pd.isnull()配合np.any()使用
          • np.any(pd.isnull(df)) # 里面如果有一个缺失值,就返回True
        • pd.notnull()配合np.all()使用
          • np.all(pd.notnull(df)) # 里面如果有一个缺失值,就返回False
    • 存在缺失值nan:
      1. 删除存在缺失值的:df.dropna(axis="rows", how="any") -> df
        • 注:不会修改原数据,需要接受返回值
      2. 替换缺失值:df.fillna(value, inplace=True) -> df
        • value:替换成的值
        • inplace=True:会修改原数据
        • inplace=False:不修改原数据,而是替换后生成新的对象
        • 例子:
          • df[col].fillna(value=df[col].mean(), inplace=True)
    • 如果缺失值没有使用NaN标记,替换:df.replace(to_replace=, value=np.nan, inplace=False) -> df
      • 参数:
        • to_replace:替换前的值
        • value:替换后的值
        • inplace=False:是否原地操作
      • 例子:比如使用'?'作为空值
        • 先替换'?'np.nan,然后继续处理
        • df.replace(to_replace='?', value=np.nan)

Q:什么时候用删除,什么时候用替换呢?
A:缺失值不是太多的时候,一般可以将缺失值删除;如果数据非常重要,且缺失值较多,一般使用替换。

特别说明:df.dropna -> df

  • 作用:用于删除 DataFrame 或 Series 中的缺失值。
  • 参数:
    • axis:指定删除缺失值的轴,0'index' 表示删除包含缺失值的行,1'columns' 表示删除包含缺失值的列。默认值为 0
    • how:指定删除缺失值的方式。
      • 'any' 表示只要有缺失值就删除整行/列
      • 'all' 表示只有当整行/列都是缺失值时才删除
      • 默认值为 'any'
    • thresh:指定行/列中非缺失值的最小数量,只有当非缺失值的数量小于这个阈值时才会删除该行/列。默认值为 None
    • subset:指定在哪些行/列中查找缺失值。默认值为 None,表示在整个 DataFrame/Series 中查找。
    • inplace:指定是否在原地修改数据。
      • 如果为 True,则不返回任何值,直接在原 DataFrame/Series 上进行修改;
      • 如果为 False,则返回一个新的 DataFrame/Series,原 DataFrame/Series 不变。默认值为 False
  • 返回值:返回一个新的 DataFrame/Series,其中已经删除了包含缺失值的行/列。

因为how=any所有我们要慎用!


案例:电影数据的缺失值处理。

数据集下载地址:https://www.kaggle.com/datasets/PromptCloudHQ/imdb-data

# 1. 读取电影数据
movie = pd.read_csv(filepath_or_buffer="./data/IMDB-Movie-Data.csv")
movie.head()

[学习笔记] 1. 机器学习前置知识_第24张图片

# 2. 判断缺失值是否存在
res = pd.notnull(movie)  # 缺失值的位置为False
res.head()

[学习笔记] 1. 机器学习前置知识_第25张图片

对于一张大表而言,使用pd.notnullpd.isnull来判断的话,我们很难知道这个df对象有没有缺失值。因此我们需要借助numpy的np.all()函数来进行判断,如果返回True,说明没有缺失值;如果返回False,说明有缺失值。

  • np.all()
    • 作用:用于测试沿指定轴的所有元素是否都为True。
    • 参数:
      • a:输入数组。
        • axis:沿着哪个轴进行计算。默认情况下,将所有元素视为一个大数组。
        • out:可选,指定结果的输出数组。
        • keepdims:可选,如果为True,则保留输入数组的维度。
    • 返回值:返回一个布尔值或布尔值数组,表示沿指定轴的所有元素是否都为True。

np.all()的示例:

import numpy as np

a = np.array([[True, True], [True, True]])
b = np.array([[True, False], [True, True]])

print(a)
"""
[[ True  True]
 [ True  True]]
"""
print(b)
"""
[[ True False]
 [ True  True]]
"""

print(np.all(a)) # True
print(np.all(b)) # False
print(np.all(b, axis=0)) # [ True False]
print(np.all(b, axis=1)) # [False  True]

# 3. 使用np.all()来判断是否存在缺失值
print(np.all(res))  # False -> 说明存在缺失值

pd.isnull()pd.notnull()是相反的,且判断函数不使用np.all(),而是np.any()

import pandas as pd
import numpy as np

# 1. 读取电影数据
movie = pd.read_csv(filepath_or_buffer="./data/IMDB-Movie-Data.csv")

# 2. 判断缺失值是否存在
res = pd.isnull(movie)  # True: 缺失值; False: 不是缺失值

# 3. 使用np.any()来判断是否存在缺失值
print(np.any(res))  # True -> 说明存在缺失值

情况一、存在缺失值nan,并且是np.nan

[学习笔记] 1. 机器学习前置知识_第26张图片

这种空值就是np.nan

在Pandas中,当你使用 read_csv() 函数读取一个CSV文件时,函数会自动将空值(例如空字符串或空单元格)转换为 np.nan

  • 方法1:删除缺失值
  • 方法2:替换缺失值
  • 方法3:替换所有缺失值(这个是最重要的,我们只执行这一步就行)
# 读取电影数据
data = pd.read_csv(filepath_or_buffer="./data/IMDB-Movie-Data.csv")

# 方法1:删除缺失值
data.dropna(how="all", inplace=True)
data

[学习笔记] 1. 机器学习前置知识_第27张图片

# 读取电影数据
data = pd.read_csv(filepath_or_buffer="./data/IMDB-Movie-Data.csv")

# 方法2:替换缺失值
"""
    替换存在缺失值的两列,替换方法有平均值、中位数
"""
data["Revenue (Millions)"].fillna(data["Revenue (Millions)"].mean(), inplace=True)
data["Metascore"].fillna(data["Metascore"].median(), inplace=True)

# 我们再看一下这两列有缺失值吗?
print(np.any(pd.isnull(data["Revenue (Millions)"])))  # False -> 没有缺失值了
print(np.any(pd.isnull(data["Metascore"])))  # False -> 没有缺失值了

[学习笔记] 1. 机器学习前置知识_第28张图片

上面这种替换缺失值有点慢,我们替换所有的缺失值:

# 读取电影数据
data = pd.read_csv(filepath_or_buffer="./data/IMDB-Movie-Data.csv")

# 方法3:替换所有缺失值(这个是最重要的,我们只执行这一步就行)
for col in data.columns:
    if np.all(pd.notnull(data[col])) == False:  # 有缺失值
        print(col)  # 打印有缺失值的列名
        data[col].fillna(data[col].mean(), inplace=True)
        
# 我们再看一下df对象还有缺失值吗?
print(np.all(pd.notnull(data)))  # True -> 没有缺失值了

[学习笔记] 1. 机器学习前置知识_第29张图片

注意:这样的替换是有bug的,如果缺失值是字符串,那么就会报错,因为字符串没有.mean()方法。

一般情况下,我们直接填充空值就可以了,不用丢弃,因为丢弃的how参数默认为"any",这会导致丢弃一整列或一整行!

情况二、不是缺失值nan,有默认标记

# 我们手动创建一个空值为?的csv文件
movie = pd.read_csv("./data/IMDB-Movie-Data.csv")
data = moive.fillna(value='?')
data.to_csv(path_or_buf="./data/test.csv")
# 或者直接使用data.to_csv(path_or_buf="./data/test.csv", rep_na='?')也可以

数据是这样的:

[学习笔记] 1. 机器学习前置知识_第30张图片

思路:

  1. 先替换'?'np.nan
    • df.replace(to_replace=, value=)
      • to_replace:替换前的值
      • value:替换后的值
  2. 再进行缺失值的处理
    • 替换所有缺失值
# 读取数据
data = pd.read_csv("./data/test.csv")

# 1. 替换'?'为np.nan
data.replace(to_replace='?', value=np.nan, inplace=True)

# 2. 再进行缺失值处理
## 替换所有缺失值
for col in data.columns:
    if np.all(pd.notnull(data[col])) == False:  # 有缺失值
        print(col)
        data[col].fillna(data[col].median(), inplace=True)
"""
Revenue (Millions)
Metascore
"""
print(np.all(pd.notnull(data)))  # True -> 没有缺失值了

[学习笔记] 1. 机器学习前置知识_第31张图片


小结

  • pd.isnullpd.notnull判断是否存在缺失值【知道】
    • np.any(pd.isnull(df)) # 里面如果有一个缺失值,就返回True
    • np.all(pd.notnull(df)) # 里面如果有一个缺失值,就返回False
  • df.dropna丢弃np.nan标记的缺失值【知道】
  • fillna填充缺失值【知道】
    • df[col].fillna(value=df[col].mean(), inplace=True)
  • df.replace替换具体某些值【知道】
    • df.replace(to_replace='?', value=np.nan)

4.11 高级处理:数据离散化

学习目标:

  1. 应用cutqcut实现数据的区间分组
  2. 应用get_dummies实现数据的one-hot编码

Q1:为什么要离散化?
A1:连续属性离散化的目的是为了简化数据结构,数据离散化技术可以用来减少给定连续属性值的个数。离散化方法经常作为数据挖掘的工具。

Q2:什么是数据的离散化
A2:连续属性的离散化就是在连续属性的值域上,将值域划分为若干个离散的区间,最后用不同的符号或整数值代表落在每个子区间中的属性值。

离散化有很多种方法,这使用一种最简单的方式去操作:

  • 原始人的身高数据:165,174,160,180,159,163,192,184
  • 假设按照身高分几个区间段:150165,165180,180~195

这样我们将数据分到了三个区间段,我可以对应的标记为矮、中、高三个类别,最终要处理成一个"哑变量"矩阵。

哑变量(Dummy Variable)通常是指在回归分析中用于转换分类变量的一种方法。在回归模型中,我们通常使用连续变量作为自变量来预测因变量的值。但是,有些情况下,自变量是分类变量,例如性别、教育水平等等。这时候,我们需要将分类变量转换成数值变量,以便于回归模型使用。

一种通用的方法是使用哑变量。哑变量本质上是将分类变量转换为虚拟二元变量,取值为0或1。对于一个有k个不同取值的分类变量,我们可以创建k-1个哑变量。其中k-1个哑变量代表了分类变量的每个可能取值,而最后一个哑变量则是参照组(也称基础组),其取值为0,代表其他所有分类变量取值不存在的情况。

例如,我们要使用一个人的性别和年龄来预测其收入水平。在此例中,性别为分类变量,只有两个取值:男和女。我们可以创建一个哑变量,记为sex_dummy,如果这个人是男性,则sex_dummy=1,否则为0。我们还需要另一个自变量——年龄,它是连续变量。然后,我们可以使用这两个自变量来建立线性回归模型,预测一个人的收入水平。

Q3:因为我们想要数据都是数字,但有些数字是字符串,可以使用哑变量来对字符串进行量化,是这个意思吗?
A3:是的,您的理解是正确的。在数据分析和机器学习中,我们通常使用数字数据进行建模和预测。但是,有些特征(例如性别、颜色或者国籍)可能是字符串或文本形式的。为了将这些非数字特征转换为数字,我们可以使用哑变量编码技术(也称为独热编码),将每个类别变成一个单独的二进制特征。这种方法可以让我们在统计分析和机器学习算法中使用这些非数值特征,并且不会引入任何偏差。


案例:股票的涨跌幅离散化

我们对股票每日的"p_change"进行离散化。

一、读取股票的数据,筛选出p_change数据

import pandas as pd
import numpy as np

data = pd.read_csv(filepath_or_buffer="./data/stock_day.csv")
p_change = data["p_change"]
print(type(p_change))  # 
p_change
"""
2018-02-27    2.68
2018-02-26    3.02
2018-02-23    2.42
2018-02-22    1.64
2018-02-14    2.05
              ... 
2015-03-06    8.51
2015-03-05    2.02
2015-03-04    1.57
2015-03-03    1.44
2015-03-02    2.62
Name: p_change, Length: 643, dtype: float64
"""

二、将股票涨跌幅数据进行分组

使用到的工具:

  1. pd.qcut(data, q) -> Categorical对象
    • 作用:一种基于样本分位数的离散化函数,它根据指定的分位数将一个连续型变量转换为分类变量。
    • 参数:
      • x:必须,要进行离散化的数据。
      • q:可选,指定分位数的数量,可以是整数或列表。
        • q=4等价于q=[0, 0.25, 0.5, 0.75, 1]。默认为4。
        • 一般会与series.value_counts()搭配使用,统计每组的个数
      • labels:可选,定义离散化后每个区间的标签。
      • retbins:可选,如果值为True,则会同时返回分组的边界。
      • precision:可选,指定小数点精度,默认值为3。
      • duplicates:可选,对于相同的分位数值是否去重,默认值为False
    • 返回值:返回值是一个Categorical对象,它包含原始数据离散化后的结果,每个区间都对应一个标签,这些标签可以通过cat属性来访问。如果指定了retbins=True,则会同时返回分组的边界。
  2. series.value_counts()
    • 作用:用于统计Series对象中每个不同元素出现的次数,返回一个新的Series对象。
    • 参数(没有必选参数):
      • normalize: bool类型,表示是否返回相对频率而非绝对频率,默认为False。
        • 如果设置为True,则将每个元素出现的次数除以总数。
      • sort: bool类型,表示是否按照元素出现的频率进行排序,默认为True。
      • ascending: bool类型,表示是否按照升序排列,默认为False。
    • 返回值:该函数返回一个新的Series对象,其中每个唯一元素都是原始Series对象中的元素,并且每个唯一元素的值是它在原始Series对象中出现的次数。
      • 如果指定了normalize=True,则每个唯一元素的值将除以原始Series对象中的元素总数。
  3. pd.cut(x, bins) -> Series
    • 作用:是Pandas中的一种离散化工具,可以根据指定的区间将数据按照一定的规则分成若干个组。离散化是数据预处理过程中常用的操作之一,它可以用于将连续型数值转换为离散型变量,从而减少统计时所需的资源和时间。
    • 参数(没有必选参数):
      • x:需要被切割的数组或者Series。
      • bins:想要把x分成的组数或组距。
      • labels: 对每个分组进行标记的列表或数组,长度必须与分组的数量相同。
      • right:第一个箱子的右侧边界和最后一个箱子的左侧边界的显式设置。
      • include_lowest:低端点是否包含在内,默认为False
        • 如果设置为True,则第一个箱子的左侧边界将包含在内。
      • precision:小数点精度。
    • 返回值:返回一个pandas.core.series.Series类型的对象,其中每个元素都对应了原始数据x中的一个值,并且根据bins参数的设定被分配到了不同的区间。这个对象还会附带一个特殊属性categories,保存有所有的区间信息;另外,还有一个value_counts()方法,可以用来快速获取每个区间中有多少个元素。

示例1:pd.qcut(data, q) -> Categorical对象

# 自行分组
qcut = pd.qcut(x=p_change, q=10)  # 分为10组
print(qcut)
"""
2018-02-27    (1.738, 2.938]
2018-02-26     (2.938, 5.27]
2018-02-23    (1.738, 2.938]
2018-02-22     (0.94, 1.738]
2018-02-14    (1.738, 2.938]
                   ...      
2015-03-06     (5.27, 10.03]
2015-03-05    (1.738, 2.938]
2015-03-04     (0.94, 1.738]
2015-03-03     (0.94, 1.738]
2015-03-02    (1.738, 2.938]
Name: p_change, Length: 643, dtype: category
Categories (10, interval[float64, right]): [(-10.030999999999999, -4.836] < (-4.836, -2.444] < (-2.444, -1.352] < (-1.352, -0.462] ... (0.94, 1.738] < (1.738, 2.938] < (2.938, 5.27] < (5.27, 10.03]
"""

# 计算分到每个组数据的个数
print("--------------------------------------")
res = qcut.value_counts()
print(type(print(res)))  # 
print(res)
"""
(-10.030999999999999, -4.836]    65
(-0.462, 0.26]                   65
(0.26, 0.94]                     65
(5.27, 10.03]                    65
(-4.836, -2.444]                 64
(-2.444, -1.352]                 64
(-1.352, -0.462]                 64
(1.738, 2.938]                   64
(2.938, 5.27]                    64
(0.94, 1.738]                    63
Name: p_change, dtype: int64
"""

示例2:pd.cut(x, bins) -> Series

# 自定义区间分组
bins = [-100, -7, -5, -3, 0, 3, 5, 7, 100]
p_counts = pd.cut(x=p_change, bins=bins)

print(type(p_counts))  # 
print(p_counts)
"""
2018-02-27      (0, 3]
2018-02-26      (3, 5]
2018-02-23      (0, 3]
2018-02-22      (0, 3]
2018-02-14      (0, 3]
                ...   
2015-03-06    (7, 100]
2015-03-05      (0, 3]
2015-03-04      (0, 3]
2015-03-03      (0, 3]
2015-03-02      (0, 3]
Name: p_change, Length: 643, dtype: category
Categories (8, interval[int64, right]): [(-100, -7] < (-7, -5] < (-5, -3] < (-3, 0] < (0, 3] < (3, 5] < (5, 7] < (7, 100]]
"""

Qpd.qcutpd.cut的区别?
Apd.qcutpd.cut 的主要区别在于分箱方式的不同。

  • pd.qcut 是等频的分箱。
  • pd.cut 是等宽的分箱

pd.qcut 则可以根据指定的分位数将数据划分为各个区间,每个区间内包含相同数量的观测值。这种方法适用于数据分布不均匀的情况,因为它可以确保每个区间内的观测值数量相等。

例如,下面的代码将一个数组分成四个等频的区间:

data = [0.1, 0.4, 0.6, 0.8, 1.2]
bins = pd.qcut(data, q=4)
print(bins)

print("------------------------------")
print(bins.value_counts())

输出:

[(0.099, 0.4], (0.099, 0.4], (0.4, 0.6], (0.6, 0.8], (0.8, 1.2]]
Categories (4, interval[float64, right]): [(0.099, 0.4] < (0.4, 0.6] < (0.6, 0.8] < (0.8, 1.2]]
------------------------------
(0.099, 0.4]    2
(0.4, 0.6]      1
(0.6, 0.8]      1
(0.8, 1.2]      1
dtype: int64

pd.cut 可以根据指定的分箱数量或分箱宽度将数据划分到各个区间。如果数据的分布不均匀,可能会导致某些区间内的观测值较少。

例如,下面的代码将一个数组分成四个等宽的区间:

import pandas as pd

data = [0.1, 0.4, 0.6, 0.8, 1.2]
bins = pd.cut(data, bins=4)
print(bins)

print("------------------------------")
print(bins.value_counts())

输出:

[(0.0989, 0.375], (0.375, 0.65], (0.375, 0.65], (0.65, 0.925], (0.925, 1.2]]
Categories (4, interval[float64, right]): [(0.0989, 0.375] < (0.375, 0.65] < (0.65, 0.925] < (0.925, 1.2]]
------------------------------
(0.0989, 0.375]    1
(0.375, 0.65]      2
(0.65, 0.925]      1
(0.925, 1.2]       1
dtype: int64

因此,pd.qcutpd.cut 的主要区别在于分箱方式的不同。

  • pd.qcut 是等宽的分箱
  • pd.cut 是等频的分箱

三、股票涨跌幅分组数据编程one-hot编码

One-hot编码是一种在机器学习和计算机视觉中广泛使用的编码技术,用于将分类变量转换为可供机器学习算法处理的数字向量。

One-hot编码的基本思想是为每个可能的分类值分配一个二进制位,并在该分类值的位置上设置为1,而在其他未选定的位置上设置为0。例如,假设我们有一个颜色特征,包括“红色”,“绿色”和“蓝色”,我们可以将它们转换成如下的向量:

  • 红色:[1, 0, 0]
  • 绿色:[0, 1, 0]
  • 蓝色:[0, 0, 1]

这样,我们就可以将分类变量作为数字向量来处理,使其可以输入到机器学习算法中进行训练和预测。此外,由于每个分类值只有一个非零元素,因此one-hot编码还具有表示唯一性的优点,避免了不同分类值之间的混淆。

语法:

  • pd.get_dummies(data, prefix=None) -> Pandas DataFrame 或 SparseDataFrame 对象
    • 作用:用于将分类变量转换为指示变量/虚拟变量(dummy variables),以便于分析。
      • 例如,如果你有一个名为“性别”的列,其中包含“男”和“女”两个值,则可以使用 pd.get_dummies() 将其转换为两个虚拟变量(哑变量):一个表示“男”的布尔值列和另一个表示“女”的布尔值列。
    • 参数:
      • data: 必需,要进行独热编码的数据。
      • prefix: 可选,添加前缀(字符串)到列名中。
      • prefix_sep: 可选,添加到前缀(如果有)与原始列名之间的分隔符。默认为'_'
      • columns: 可选,定义哪些列需要进行独热编码。如果不指定,则对所有对象或类别类型的列进行编码。
      • sparse: 可选,默认为 False 。返回稠密数组或稀疏矩阵。
      • drop_first: 可选,默认为 False。从每个类别变量中删除第一个类别,以避免共线性的问题。
    • 返回值:返回值是一个 Pandas DataFrame 或 SparseDataFrame 对象,其中每个类别变量都被转换成了一个或多个虚拟变量,并且列名已经被修改以反映虚拟变量的名称。如果指定了 drop_first 参数,则删除第一个类别变量,并且每个类别变量都将被转换为 k − 1 k-1 k1 个虚拟变量( k k k 是类别变量中的唯一值数)。
# 得出一个one-hot编码矩阵
dummies = pd.get_dummies(data=p_counts, prefix="rise")
dummies

[学习笔记] 1. 机器学习前置知识_第32张图片

对于每一行,只有一列的值是1,其他列都是0。

上面因为传入的是p_count,因此每个日期都会有一个one-hot。


小结

  • 数据离散化【知道】
    • 可以用来减少给定连续属性值的个数
    • 在连续属性的值域上,将值域划分为若干个离散的区间,最后用不同的符号或整数值代表落在每个子区间中的属性值
  • qcut. cut实现数据分组【知道】
    • pd.qcut:大致分为相同的几组
    • pd.cut:自定义分组区间
  • df.get_dummies实现哑变量矩阵【知道】

4.12 高级处理:合并

学习目标:

  • 应用pd.concat实现数据的合并
  • 应用pd.merge实现数据的合并

使用场景:如果你的数据由多张表组成,那么有时候需要将不同的内容合并在一起分析。

方法:

  1. pd.concat([data1, data2], axis=1)
  2. pd.merge(left, right, how="inner", on=None)

一、pd.concat()实现数据合并

  • pd.concat([data1, data2], axis=1) -> DF/Series
    • 作用:用于合并(连接)数据框或系列对象的函数。它可以将多个数据框或系列沿着一条轴(默认是行轴 axis=0)进行拼接,生成一个新的数据框或系列。
    • 参数:
      • objs: 要合并的数据框或系列对象列表,必选参数。
      • axis: 合并的轴,默认为 0。
        • axis=0'index' 时,表示沿着行索引进行连接,即将多个 DataFrame 纵向堆叠
        • axis=1'columns' 时,表示沿着列索引进行连接,即将多个 DataFrame 横向拼接
      • join: 指定合并的方式,有 inner 和 outer 两种方式,inner 表示内连接,outer 表示外连接,默认为 outer。
      • ignore_index: 是否忽略原来的索引,如果设为 True,则合并后的数据框(系列)的索引会从 0 开始重新编号。
      • keys: 为合并前的各个数据框(系列)添加标签,以区分来源,默认为 None。
      • sort: 在合并后是否对数据进行排序,默认为 False。
    • 返回值:返回一个合并后的新数据框或系列对象。

比如我们将刚才处理好的one-hot编码与原数据合并:

pd.concat(objs=[data, dummies], axis=1)  # 按列合并

[学习笔记] 1. 机器学习前置知识_第33张图片

二、pd.merge()实现数据合并

  • pd.merge(left, right, how="inner", on=None) -> DF
    • 作用:用于合并两个或多个 DataFrame 的函数。该函数基于类似 SQL 中 JOIN 操作的概念,将两个或多个 DataFrame 中的行和列进行组合,生成一个新的 DataFrame。
    • 参数:
      • left:要合并的左侧 DataFrame。
      • right:要合并的右侧 DataFrame。
      • on:指定连接的列名,即根据哪些列进行合并。如果省略此参数,则使用两个 DataFrame 中公共的列。
      • how:指定连接类型,包括 inner(内连接)、outer(外连接)、left(左连接)和 right(右连接)。默认为 inner。
      • suffixes:指定两个 DataFrame 中具有相同列名的列的后缀字符串。默认值为(“_x”, “_y”)。
    • 返回值:返回一个新的 DataFrame,其中包含两个或多个 DataFrame 中的所有行和列。返回的 DataFrame 将根据指定的连接键进行合并,这些键可以是一个或多个列。返回的 DataFrame 包含每个输入 DataFrame 中的所有列,也可以根据需要重命名重复的列名。
合并方式 SQL Join Name 说明
left LEFT OUTER JOIN 仅使用左侧 DataFrame 的键,类似于 SQL 左外连接;保留键顺序
right RIGHT OUTER JOIN 仅使用右侧 DataFrame 的键,类似于 SQL 右外连接;保留键顺序
outer FULL OUTER JOIN 使用两个 DataFrame 的键的并集,类似于 SQL 完外连接;按字典顺序对键进行排序
inner INNER OUTER JOIN 使用两个 DataFrame 的键的交集,类似于 SQL 内连接;保留左侧键的顺序
left = pd.DataFrame({"key1": ["K0", "K0", "K1", "K2"],
                      "key2": ["K0", "K1", "K0", "K1"],
                      'A': ["A0", "A1", "A2", "A3"],
                      'B': ["B0", "B1", "B2", "B3"]})

right = pd.DataFrame({"key1": ["K0", "K1", "K1", "K2"],
                      "key2": ["K0", "K0", "K0", "K0"],
                      'C': ["C0", "C1", "C2", "C3"],
                      'D': ["D0", "D1", "D2", "D3"]})

print(left)
"""
  key1 key2   A   B
0   K0   K0  A0  B0
1   K0   K1  A1  B1
2   K1   K0  A2  B2
3   K2   K1  A3  B3
"""

print(right)
"""
  key1 key2   C   D
0   K0   K0  C0  D0
1   K1   K0  C1  D1
2   K1   K0  C2  D2
3   K2   K0  C3  D3
"""
  1. 内连接(默认)

如果key1和key2都相同,就进行拼接,不相同的不进行拼接(重复的也要拼接)

只保留两个 DataFrame 中都存在的行。

# 内连接(默认)
result = pd.merge(left, right, on=["key1", "key2"])
print(result)
"""
  key1 key2   A   B   C   D
0   K0   K0  A0  B0  C0  D0
1   K1   K0  A2  B2  C1  D1
2   K1   K0  A2  B2  C2  D2
"""

[学习笔记] 1. 机器学习前置知识_第34张图片

  1. 左连接

以左表为主,右表没有的则设置为空值NaN(重复的也要拼接)

保留左侧 DataFrame中所有列的值,并用 NaN 填充缺失值。

# 左连接
result = pd.merge(left, right, on=["key1", "key2"], how="left")
print(result)
"""
  key1 key2   A   B    C    D
0   K0   K0  A0  B0   C0   D0
1   K0   K1  A1  B1  NaN  NaN
2   K1   K0  A2  B2   C1   D1
3   K1   K0  A2  B2   C2   D2
4   K2   K1  A3  B3  NaN  NaN
"""

[学习笔记] 1. 机器学习前置知识_第35张图片

  1. 右连接

以右表为主,左表没有的则设置为空值NaN(重复的也要拼接)

保留右侧 DataFrame中所有列的值,并用 NaN 填充缺失值。

# 右连接
result = pd.merge(left, right, on=["key1", "key2"], how="right")
print(result)
"""
  key1 key2    A    B   C   D
0   K0   K0   A0   B0  C0  D0
1   K1   K0   A2   B2  C1  D1
2   K1   K0   A2   B2  C2  D2
3   K2   K0  NaN  NaN  C3  D3
"""

[学习笔记] 1. 机器学习前置知识_第36张图片

  1. 外连接

所有数据都合并起来,没有的设置为NaN(与内连接不同,内连接没有的就不合并了)

保留两个 DataFrame 中所有的值,并用 NaN 填充缺失值。

# 外连接
result = pd.merge(left, right, on=["key1", "key2"], how="outer")
print(result)
"""
  key1 key2    A    B    C    D
0   K0   K0   A0   B0   C0   D0
1   K0   K1   A1   B1  NaN  NaN
2   K1   K0   A2   B2   C1   D1
3   K1   K0   A2   B2   C2   D2
4   K2   K1   A3   B3  NaN  NaN
5   K2   K0  NaN  NaN   C3   D3
"""

[学习笔记] 1. 机器学习前置知识_第37张图片

三、pd.concat()pd.merge()的联系

pd.concatpd.merge都是Pandas库中用于合并数据的函数,二者的相同点和不同点如下:

  • 相同点
    • 用途:pd.concatpd.merge都用于将多个数据集合并成一个。
    • 参数:两个函数都有类似的参数,包括要合并的数据集、合并方式、删除重复值等。
  • 不同点:
    • 合并方式:pd.concat默认以行方向(纵向)拼接数据,可以通过axis参数修改为以列方向(横向)拼接;而pd.merge默认以列方向(横向)拼接数据,可以通过how参数修改为其他合并方式(如内连接、左连接、右连接、外连接)。
    • 数据来源:pd.concat主要用于将多个来自相同表结构的数据源合并,而pd.merge则主要用于将来自不同数据源的数据按照特定条件进行合并。
    • 数据重叠:当待合并的数据集中存在相同的列名时,pd.concat会直接将这些列合并到一起,而pd.merge则需要指定要合

小结

  • pd.concat([数据1, 数据2], axis=0)【知道】
  • `pd.merge(left, right, how=“inner”, on=)【知道】
    • how – 以何种方式连接
    • on – 连接的键的依据是哪几个

4.13 高级处理:交叉表与透视表

学习目标:应用crosstab和pivot_table实现交叉表与透视表

Q:交叉表与透视表什么作用?
A:在 Pandas 中,交叉表(crosstab)和透视表(pivot table)都是用来对数据进行汇总和分组的工具。

  • 交叉表是一种用于计算两个或更多因素的简单交叉表。默认情况下,它会计算因素的频率表,除非传递了值数组和聚合函数。它不一定需要一个 DataFrame 作为输入,也可以接受类似数组的对象作为行和列。
  • 透视表则是一种类似于电子表格的透视表,它允许对数据进行多维汇总。它需要一个 DataFrame 作为输入,并且可以通过传递列名作为字符串来指定索引/列/值。
  • 总之,交叉表和透视表都可以用来对数据进行汇总和分组,但它们在输入数据类型和使用方式上略有不同。可以根据实际情况选择使用哪种工具。

举例:探究股票的涨跌与星期几有关?

以下图当中表示,week代表星期几,1, 0代表这一天股票的涨跌幅是好还是坏,里面的数据代表比例可以理解为涨跌幅好坏的比例。

[学习笔记] 1. 机器学习前置知识_第38张图片

这样查看数据可能不够直观,可以画个图展示。

[学习笔记] 1. 机器学习前置知识_第39张图片


  1. pd.crosstab(value1, value2)
  2. df.pivot_table([], index=[])

一、pd.crosstab(value1, value2) -> df

  • 作用:用于计算两个或多个因素的简单交叉表的函数。默认情况下,它会计算因素的频率表,除非传递了值数组和聚合函数。
  • 参数:
    • index:用于分组行的值,可以是类似数组的对象、Series 或多个数组/Series 的列表。
    • columns:用于分组列的值,可以是类似数组的对象、Series 或多个数组/Series 的列表。
    • values:可选,要根据因素进行聚合的值数组。需要指定 aggfunc
    • rownames:可选,行名称序列。
    • colnames:可选,列名称序列。
    • aggfunc:可选,聚合函数。如果指定,则需要指定 values
    • margins:布尔值,默认为 False。是否添加行/列边距(小计)。
    • margins_name:字符串,默认为 ‘All’。当 margins=True 时,包含总计的行/列的名称。
    • dropna:布尔值,默认为 True。不包括所有条目都为 NaN 的列。
    • normalize:布尔值,默认为 False。通过将所有值除以值之和来进行归一化。
  • 返回值:返回一个 DataFrame,表示数据的交叉表。

例如,假设我们有一个 DataFrame,其中包含有关人口普查数据的信息:

import pandas as pd

data = {'Gender': ['Male', 'Male', 'Female', 'Female', 'Male'],
        'Age': ['Young', 'Young', 'Old', 'Young', 'Old'],
        'Income': [10, 20, 30, 40, 50]}

df = pd.DataFrame(data)
print(df)
"""
   Gender    Age  Income
0    Male  Young      10
1    Male  Young      20
2  Female    Old      30
3  Female  Young      40
4    Male    Old      50
"""

我们可以使用 pd.crosstab() 函数来计算性别和年龄分组的收入平均值:

result = pd.crosstab(index=df['Gender'], columns=df['Age'], values=df['Income'], aggfunc='mean')
print(result)

输出结果为:

Age      Old  Young
Gender             
Female  30.0   40.0
Male    50.0   15.0

这表示男性中年龄为“Old”的人的平均收入为50,年龄为“Young”的人的平均收入为15;女性中年龄为“Old”的人的平均收入为30,年龄为“Young”的人的平均收入为40。

二、df.pivot_table([], index=[]) -> df

  • 作用:创建数据透视表,即原有的DataFrame的列分别作为行索引和列索引,然后对指定的列应用聚集函数。
  • 参数:
    • index 参数用于指定透视表的行索引。每个 pivot_table 必须拥有一个 index
    • values 参数用于指定需要聚合的数据列。
    • columns 参数用于指定透视表的列层次字段,它不是一个必要参数,作为一种分割数据的可选方式。
    • aggfunc 参数用于指定对数据聚合时进行的函数操作。如果未设置 aggfunc,则默认计算均值(aggfunc='mean')。
  • 返回值:返回一个新的 DataFrame,其中包含根据指定参数创建的数据透视表。

下面是一个简单的例子,演示如何使用 df.pivot_table() 函数创建数据透视表:

import pandas as pd

# 创建一个 DataFrame
df = pd.DataFrame({
    "A": ["foo", "foo", "foo", "foo", "foo", "bar", "bar", "bar", "bar"],
    "B": ["one", "one", "one", "two", "two", "one", "one", "two", "two"],
    "C": ["small", "large", "large", "small", "small", "large", "small", "small", "large"],
    "D": [1, 2, 2, 3, 3, 4, 5, 6, 7],
    "E": [2, 4, 5, 5, 6, 6, 8, 9, 9]
})
print(df)
"""
     A    B      C  D  E
0  foo  one  small  1  2
1  foo  one  large  2  4
2  foo  one  large  2  5
3  foo  two  small  3  5
4  foo  two  small  3  6
5  bar  one  large  4  6
6  bar  one  small  5  8
7  bar  two  small  6  9
8  bar  two  large  7  9
"""

# 创建数据透视表
table = pd.pivot_table(df, values='D', index=['A', 'B'], columns=['C'], aggfunc=np.sum)

# 显示数据透视表
print(table)

上面的代码创建了一个简单的 DataFrame,然后使用 df.pivot_table() 函数创建了一个数据透视表。在这个例子中,我们指定了 values='D' 来表示我们想要对 'D' 列进行聚合。我们还指定了 index=['A', 'B'] 来表示我们想要根据 'A''B' 列来分组数据。最后,我们指定了 columns=['C'] 来表示我们想要根据 'C' 列来创建透视表的列。

运行上面的代码会输出以下结果:

C      large  small
A   B              
bar one    NaN    5.0
    two    7.0    6.0
foo one    4.0    1.0
    two    NaN    6.0

这就是一个简单的例子,演示了如何使用 df.pivot_table() 函数创建数据透视表。可以根据自己的需求调整参数来创建不同的数据透视表。

三、案例分析

  • 准备两列数据,星期数据以及涨跌幅是好是坏数据
  • 进行交叉表计算
# 寻找星期几跟股票涨跌的关系
# 1. 先根据对应的日期获取当天是星期几
date = pd.to_datetime(data.index).weekday + 1  # 不从0开始,而是从1开始

# 创建"week"列
data["week"] = date

# 2. 把p_change按照分类,其中以0为界限
data["pos_neg"] = np.where(data["p_change"] > 0, 1, 0)

# 通过交叉表找寻两列数据的关系
count = pd.crosstab(index=data["week"], columns=data["pos_neg"])
print(count)
"""
pos_neg   0   1
week           
1        63  62
2        55  76
3        61  71
4        63  65
5        59  68
"""

Q:但是我们看到count只是每个星期日子的好坏天数,并没有得到比例,该怎么去做?
A:对每个星期几的总天数求和,运用除法运算求出比例。

# 算术运算,先求和
sum = count.sum(axis=1).astype(np.float32)
print(sum)
"""
week
1    125.0
2    131.0
3    132.0
4    128.0
5    127.0
dtype: float32
"""

# 进行相除操作,得出比例
res = count.div(sum, axis=0)
print(res)
"""
pos_neg         0         1
week                       
1        0.504000  0.496000
2        0.419847  0.580153
3        0.462121  0.537879
4        0.492188  0.507812
5        0.464567  0.535433
"""

为了直观的体现交叉表,画柱状图:

import matplotlib.pyplot as plt

fig, axes = plt.subplots(1, 2, figsize=(20, 8))
res.plot(kind="bar", ax=axes[0], title="stacked=False")
res.plot(kind="bar", ax=axes[1], stacked=True, title="stacked=True")

[学习笔记] 1. 机器学习前置知识_第40张图片


使用透视表,刚才的过程更加简单。

# 通过透视表,将整个过程变得更简单一些
res = data.pivot_table(["pos_neg"], index="week")
print(res)
"""
       pos_neg
week          
1     0.496000
2     0.580153
3     0.537879
4     0.507812
5     0.535433
"""

res.plot(kind="bar", title="stacked=False")
plt.show()

[学习笔记] 1. 机器学习前置知识_第41张图片


小结

  • 交叉表与透视表的作用【知道】
    • 交叉表:计算一列数据对于另外一列数据的分组个数。
    • 透视表:指定某一列对另一列的关系。

4.14 高级处理:分组与聚合

学习目标:应用groupby和聚合函数实现数据的分组与聚合。

分组与聚合通常是分析数据的一种方式,通常与一些统计函数一起使用,查看数据的分组情况。

在Pandas中,分组(grouping)和聚合(aggregating)是数据处理中非常重要的工具。它们可以帮助我们对数据进行分类汇总,提取有用信息,并生成新的数据集。

分组的作用:

在实际数据处理过程中,我们通常需要根据某些特征把数据进行分组以便后续的操作。例如,在一个销售数据表中,我们可能希望按照不同的产品类型或销售时间来分组数据。这样就可以更好地理解和分析数据,找出其中的规律和趋势。

聚合的作用:

在分组之后,我们通常会对每个分组进行统计计算,例如求和、平均值、最大值等等。这就是聚合的作用。Pandas提供了多种内置的聚合函数,例如sum()mean()max()等等,可以方便地对每个分组进行计算,并生成新的数据集。

应用场景:

【简单理解】分组和聚合是数据处理中非常常见的操作,通常应用于以下场景:

  • 对大型数据集进行分类汇总,并提取有用信息。
  • 对数据集进行统计计算,并生成新的数据集。
  • 简化数据集,方便后续的分析和可视化。

【总结】Pandas中,分组与聚合的应用场景包括:

  1. 按类别对数据进行统计:比如对于一个电商网站的销售数据,可以按照商品种类进行分组,然后对每个类别的销售总额、平均价、最大价等进行聚合。
  2. 数据透视表:类似Excel中的数据透视表,通过指定行和列索引以及聚合函数,将数据进行多维度的汇总和统计。
  3. 时间序列分析:对于时间序列数据,可以按照年、月、日等时间维度进行分组,然后对每个时间段内的数据进行聚合,得到相应的统计结果。
  4. 数据清洗:在数据清洗过程中,有时需要按照某些规则对数据进行分组,并对每个分组中的数据进行处理和操作。
  5. 数据可视化:在数据可视化中,通过对数据进行分组和聚合,可以得到更加直观的图表展示效果。

分组与聚合是Pandas中非常重要的功能,它们可以帮助我们对大量数据进行快速高效的统计和分析,并得到有意义的

刚才的交叉表与透视表也有分组的功能,所以算是分组的一种形式,只不过他们主要是计算次数或者计算比例。

Q:什么是分组与聚合?
A:看下面这张图片。

[学习笔记] 1. 机器学习前置知识_第42张图片


一、分组API

df.groupby(by, as_index=True) -> DataFrameGroupBy/SeriesGroupBy

  • 作用:用于根据指定的列或索引对数据进行分组。该方法可以将数据按照某些标准分为若干组,并对每一组进行统计分析。
  • 参数:
    • by:用于指定分组所依据的列或索引。可以传入单个列名多个列名组成的列表或元组、Series对象、DataFrame对象、函数或函数列表等。
    • axis:用于指定分组依据的轴方向。默认为0表示按列进行分组,1表示按行进行分组。
    • level:用于指定分组所依据的索引层级。当分组依据为多层索引时使用。
    • sort:用于指定是否对结果进行排序,默认为True。
    • group_keys:用于指定是否要在结果中显示分组键,默认为True。
    • as_index:用于指定是否将分组键设置为结果的索引,默认为True。
    • squeeze:用于指定是否对结果进行压缩,默认为False。
  • 返回值:返回值为一个 DataFrameGroupBy 或者 SeriesGroupBy 对象。这个对象可以进行很多操作,如聚合(aggregation)、过滤(filtering)和转换(transformation)等。也可以通过 groups 属性查看分组情况。

二、聚合API

Pandas提供了许多聚合函数用于数据处理和分析。以下是常见的一些聚合函数:

  1. sum():计算数据的总和。
  2. mean():计算数据的平均值。
  3. median():计算数据的中位数。
  4. min():找出数据的最小值。
  5. max():找出数据的最大值。
  6. count():计算数据的数量。
  7. std():计算数据的标准差。
  8. var():计算数据的方差。
  9. describe():统计数据的基本信息,如平均数、标准差、最小值、最大值等。

以上只是部分常用的聚合函数,Pandas还提供了一个综合的聚合函数agg。它可以对单列或多列进行单一或多个不同的聚合函数处理,常用函数如minmaxmedianstdcountsizesum等,直接用函数名加引号即可,如果有多个函数,用逗号隔开:

  • df.agg(func=None, axis=0, *args, **kwargs)
    • 作用:用于对数据进行聚合操作
    • 参数:
      • func:用于聚合数据的函数,函数必须在DataFrame和apply函数中均可用;输入可以是函数名称、函数名称的字符串、函数组成的列表、或轴标签字典。
      • axis:取值为0或’index’(函数作用于每列),1或’columns’(函数作用于每行),默认为0。
      • *args:传递函数的位置参数。
      • **kwargs:传递关键字的位置参数。
    • 返回值:返回值可以是scalar(当在Series.agg()中使用单个函数时),Series(当在DataFrame.agg()中使用单个函数时)或DataFrame(当在DataFrame.agg()中使用多个函数时)。

例如,我们可以使用以下代码对数据进行聚合:

df.agg({'列名1': 'sum', '列名2': 'mean'})

上面的代码将对列名1列求和,对列名2列求平均值。

分组和聚合必须放在一起说,抛开聚合函数,分组函数就没有什么意义了!


三、案例:不同颜色的不同笔的价格数据。

col = pd.DataFrame({
                    "color": ["white", "red", "green", "red", "green"],
                    "oibject": ["pen", "pencil", "pencil", "ashtray", "pen"],
                    "price1": [5.56, 4.20, 1.30, 0.56, 2.75],
                    "price2": [4.75, 4.12, 1.60, 0.75, 3.15]})
print(col)
"""
   color  oibject  price1  price2
0  white      pen    5.56    4.75
1    red   pencil    4.20    4.12
2  green   pencil    1.30    1.60
3    red  ashtray    0.56    0.75
4  green      pen    2.75    3.15
"""

进行分组:对颜色分组,对"price1"进行聚合:

# 分组、求平均值
# DataFrame的分组
res1 = col.groupby(by=["color"])
print(type(res1))  # 
print(res1)  # 
res1 = res1["price1"].mean()
print(res1)
"""
color
green    2.025
red      2.380
white    5.560
Name: price1, dtype: float64
"""


# Series的分组
res2 = col["price1"]
print(type(res2))  # 
print(res2)
"""
0    5.56
1    4.20
2    1.30
3    0.56
4    2.75
Name: price1, dtype: float64
"""
res2 = res2.groupby(by=col["color"]).mean()
print(res2)
"""
color
green    2.025
red      2.380
white    5.560
Name: price1, dtype: float64
"""


# 分组,数据的结构不变
res3 = col.groupby(by=["color"], as_index=False)["price1"].mean()
print(res3)
"""
   color  price1
0  green   2.025
1    red   2.380
2  white   5.560
"""

四、案例:星巴克零售店铺数据

现在我们有一组关于全球星巴克店铺的统计数据,如果我想知道美国的星巴克数量和中国的哪个多,或者我想知道中国每个省份星巴克的数量的情况,那么应该怎么办?

数据集下载地址:https://www.kaggle.com/datasets/starbucks/store-locations

Step1:读取数据

# step1:读取数据
starbucks = pd.read_csv("./data/starbucks_directory.csv")

[学习笔记] 1. 机器学习前置知识_第43张图片

Step2:进行分组聚合

# Step2:进行分组聚合
# 按照国家分组,求出每个国家的零售店数量
count = starbucks.groupby(by="Country").count()

[学习笔记] 1. 机器学习前置知识_第44张图片

Step3:画图显示结果

# Step3:画图显示结果
count["Brand"].plot(kind="bar", figsize=(20, 8))
plt.show()

[学习笔记] 1. 机器学习前置知识_第45张图片


假设我们加入省市一起进行分组:

# 设置多个索引
counts = starbucks.groupby(["Country", "State/Province"]).count()
counts.head()

[学习笔记] 1. 机器学习前置知识_第46张图片

Q:仔细观察这个结构,与我们前面讲的哪个结构类似?
A:与前面的MultiIndex结构类似

画图:

counts["Brand"].plot(kind="bar", figsize=(20, 8))
plt.show()

[学习笔记] 1. 机器学习前置知识_第47张图片

问题:

如果在分组聚合后得到了一个具有两个索引(即两个横坐标)的DataFrame,那么直接绘图可能会导致图形混乱。

解决方案:

在这种情况下,可以考虑使用reset_index函数将其中一个索引重置为普通列,然后再进行绘图。

例如,如果你想要绘制一个柱状图来展示每个国家/地区的星巴克门店数量,可以使用以下代码:

print(counts.index)  # ['Country', 'State/Province']
print(type(counts))  # 
# 重置索引(将其变为普通的列)
country_counts = counts.reset_index(level=1)  # 让'State/Province'从索引变为普通列
print(type(country_counts))  # 
print(country_counts.index)
"""
Index(['AD', 'AE', 'AE', 'AE', 'AE', 'AE', 'AE', 'AE', 'AR', 'AR',
       ...
       'US', 'US', 'US', 'US', 'US', 'US', 'US', 'VN', 'VN', 'ZA'],
      dtype='object', name='Country', length=545)
"""
"""
注意:在执行`country_counts = counts.reset_index(level=1)`这行代码后,
`country_counts`变量中存储的不再是一个分组聚合后的DataFrame,而是一个普通的DataFrame。

因此我们不能直接plot,因为横坐标已经不再是分组聚合后的了,我们需要重新分组聚合
"""

# 重新分组聚合:计算每个国家/地区的星巴克门店数量
country_counts = country_counts.groupby("Country").count()  

# 绘制柱状图
country_counts["Brand"].plot(kind="bar")

# 显示图形
plt.show()

上面的代码首先使用reset_index函数将第二个索引(即State/Province列)重置为普通列。然后,计算了每个国家/地区的星巴克门店数量,并使用plot函数绘制了一个柱状图。最后,使用plt.show()函数显示了图形。

[学习笔记] 1. 机器学习前置知识_第48张图片

注意:在执行country_counts = counts.reset_index(level=1)这行代码后,
country_counts变量中存储的不再是一个分组聚合后的DataFrame,而是一个普通的DataFrame

因此我们不能直接plot,因为横坐标已经不再是分组聚合后的了,我们需要重新分组聚合

# 重置索引
country_counts = counts.reset_index()  # 重置索引

# 重新分组聚合:计算每个国家/地区的星巴克门店数量
country_counts = country_counts.groupby("State/Province").count()  

# 绘制柱状图
country_counts["Brand"].plot(kind="bar", figsize=(20, 8))

# 隐藏横坐标标签(省份太多了,显示的就会很乱)
plt.xticks([])

# 显示图形
plt.show()

[学习笔记] 1. 机器学习前置知识_第49张图片


小结

  • groupby进行数据的分组【知道】
    • pandas中,抛开聚合谈分组,无意义

4.15 案例:综合应用

4.15.1 需求

现在我们有一组从2006年到2016年1000部最流行的电影数据。

  • 问题1:我们想知道这些电影数据中评分的平均分,导演的人数等信息,我们应该怎么获取?
  • 问题2:对于这一组电影数据,如果我们想rating,runtime的分布情况,应该如何呈现数据?
  • 问题3:对于这一组电影数据,如果我们希望统计电影分类(genre)的情况,应该如何处理数据?

数据集下载地址:https://www.kaggle.com/datasets/PromptCloudHQ/imdb-data

4.15.2 实现

一、获取数据

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt


# 读取文件
path = "./data/IMDB-Movie-Data.csv"
df = pd.read_csv(path)
df.head()

[学习笔记] 1. 机器学习前置知识_第50张图片

二、问题1:我们想知道这些电影数据中评分的平均分,导演的人数信息,我们应该怎么获取?

  1. 得出评分的平均分:使用mean函数
res = df["Rating"].mean()
print(res)  # 6.723199999999999
  1. 得出导演人数信息:求出唯一值,然后进行形状获取
# 导演人数
# 方法1
res = df["Director"].unique()
print(type(res))  # 
counts = res.shape[0]
print(counts)  # 644

# 方法2
res = np.unique(df["Director"])
print(type(res))  # 
counts = res.shape[0]
print(counts)  # 644

三、问题2:对于这一组电影数据,如果我们想知道Rating,Runtime (Minutes)的分布情况,应该如何呈现数据?

分布情况应该考虑直方图。

from pylab import mpl
# 设置中文字体
mpl.rcParams["font.sans-serif"] = ["SimHei"]
# 设置正常显示符号
mpl.rcParams["axes.unicode_minus"] = False

df["Rating"].plot(kind="hist", figsize=(20, 8))
plt.show()

[学习笔记] 1. 机器学习前置知识_第51张图片

上面这幅图像是有问题的, x = 5 x=5 x=5应该是对应的bin的边,现在偏右了。

下面是合格的直方图要求:

  • 合适的间隔: 直方图中的间隔(bin)应该足够小,以便能够捕捉到数据中的所有变化,并且足够大,以便在不失真地呈现数据的情况下,使直方图易于理解。如果间隔太小,则直方图将变得嘈杂混乱,难以解读;如果间隔太大,则可能会掩盖数据的细节。

  • 相应的轴: 直方图必须具有明确的轴标签,包括x轴和y轴,以便用户可以轻松地了解其内容。轴标题应该准确地描述数据的含义,并使用适当的度量单位。

  • 可视化效果: 直方图的外观应该整洁美观,颜色、字体和线条等元素应该恰如其分,以便最大程度上展示数据的特征。

  • 合适的统计分析: 直方图不能仅仅是数据的可视化表示,还需要结合统计分析方法,如均值、中位数、标准差等,以便客户能够更好地理解数据并做出正确的决策。


综上所述,我们需要更加复杂的操作,所以我们不直接plot(),而是借助Matplotlib画图。

# 借助Matplotlib画图

# 设置画布大小
plt.figure(figsize=(15, 8))

# 绘制直方图
plt.hist(x=df["Rating"], bins=25, alpha=0.7, color='green')

# 修改刻度间隔
max_ = df["Rating"].max() + 0.5
min_ = df["Rating"].min() - 0.5
t1 = np.arange(min_, max_, 0.5)
plt.xticks(t1, fontsize=14)

# 添加标签和标题
plt.xlabel("电影评分Rating", fontsize=14)
plt.ylabel("对应评分出现次数", fontsize=14)
plt.title("电影评分Rating直方图", fontsize=16)

# 添加网格
plt.grid(visible=True, alpha=0.5)

# 显示图像
plt.show()

[学习笔记] 1. 机器学习前置知识_第52张图片


Runtime (Minutes)的分布情况也是同理:

# Runtime (Minutes)的分布情况

plt.figure(figsize=(15, 8))
bins_num = 25
plt.hist(x=df["Runtime (Minutes)"].values, bins=bins_num, alpha=0.7)

max_ = df["Runtime (Minutes)"].max()
min_ = df["Runtime (Minutes)"].min()

# 修改刻度间隔
step = np.linspace(start=min_, stop=max_, num=bins_num+1)
plt.xticks(step, fontsize=14)

# 修改其他
plt.xlabel("播放时长(分钟)", fontsize=14)
plt.ylabel("对应出现次数", fontsize=14)
plt.title("电影播放时长直方图", fontsize=18)
plt.grid(visible=True, alpha=0.5)

# 显示图像
plt.show()

[学习笔记] 1. 机器学习前置知识_第53张图片

四、问题3:对于这一组电影数据,如果我们希望统计电影分类(genre)的情况,应该如何处理数据?

[学习笔记] 1. 机器学习前置知识_第54张图片

从图中可以看到,Genre不像Director,只有一个字符串。Genre中有多个字符串,所以解决起来比较麻烦,我们需要对字符串进行拆分。

思路分析

  1. 创建一个全为0的dataframe,列索引置为电影的分类:temp_df
  2. 遍历每一部电影,temp_df中把分类出现的列的值置为1
  3. 求和、绘图

具体实现

  1. 创建一个全为0的dataframe,列索引置为电影的分类:temp_df
# 1. 创建一个全为0的dataframe,列索引置为电影的分类:`temp_df`

# 进行字符串分割
tmp_lst = [values.split(',') for values in df["Genre"]]
# print(tmp_lst)
"""
[['Action', 'Adventure', 'Sci-Fi'],
 ['Adventure', 'Mystery', 'Sci-Fi'],
 ['Horror', 'Thriller'],
 ['Animation', 'Comedy', 'Family'],
 ['Action', 'Adventure', 'Fantasy'],
 ['Action', 'Adventure', 'Fantasy'],
 ['Comedy', 'Drama', 'Music'],
 ...
]
"""

# 获取电影的分类
genre_lst = []
for lst in tmp_lst:
    for val in lst:
        genre_lst.append(val)
genre_lst = np.unique(genre_lst)
# 也可以直接使用列表推导式来进行
# genre_lst = np.unique([i for j in tmp_lst for i in j])
print(genre_lst)
"""
['Action' 'Adventure' 'Animation' 'Biography' 'Comedy' 'Crime' 'Drama'
 'Family' 'Fantasy' 'History' 'Horror' 'Music' 'Musical' 'Mystery'
 'Romance' 'Sci-Fi' 'Sport' 'Thriller' 'War' 'Western']
"""

# 增加新的列
tmp_df = pd.DataFrame(data=np.zeros(shape=[df.shape[0], genre_lst.shape[0]]), columns=genre_lst)
print(tmp_df.head())
"""
   Action  Adventure  Animation  Biography  Comedy  Crime  ...  Romance  Sci-Fi  Sport  Thriller  War  Western
0     0.0        0.0        0.0        0.0     0.0    0.0  ...      0.0     0.0    0.0       0.0  0.0      0.0
1     0.0        0.0        0.0        0.0     0.0    0.0  ...      0.0     0.0    0.0       0.0  0.0      0.0
2     0.0        0.0        0.0        0.0     0.0    0.0  ...      0.0     0.0    0.0       0.0  0.0      0.0
3     0.0        0.0        0.0        0.0     0.0    0.0  ...      0.0     0.0    0.0       0.0  0.0      0.0
4     0.0        0.0        0.0        0.0     0.0    0.0  ...      0.0     0.0    0.0       0.0  0.0      0.0

[5 rows x 20 columns]
"""
  1. 遍历每一部电影,temp_df中把分类出现的列的值置为1
# 2. 遍历每一部电影,`temp_df`中把分类出现的列的值置为1


for i in range(df.shape[0]):
    tmp_df.iloc[i].loc[tmp_lst[i]] = 1
    """
    tmp_df.iloc[i]:选取第i行
    tmp_df.iloc[i].loc[tmp_lst[i]]:选取第i行的tmp_lst[i]列(可以是lst,多次选列)
    tmp_df.iloc[i].loc[tmp_lst[i]] = 1:赋值为1
    """
print(tmp_df.head())
"""
   Action  Adventure  Animation  Biography  Comedy  Crime  ...  Romance  Sci-Fi  Sport  Thriller  War  Western
0     1.0        1.0        0.0        0.0     0.0    0.0  ...      0.0     1.0    0.0       0.0  0.0      0.0
1     0.0        1.0        0.0        0.0     0.0    0.0  ...      0.0     1.0    0.0       0.0  0.0      0.0
2     0.0        0.0        0.0        0.0     0.0    0.0  ...      0.0     0.0    0.0       1.0  0.0      0.0
3     0.0        0.0        1.0        0.0     1.0    0.0  ...      0.0     0.0    0.0       0.0  0.0      0.0
4     1.0        1.0        0.0        0.0     0.0    0.0  ...      0.0     0.0    0.0       0.0  0.0      0.0

[5 rows x 20 columns]
"""

print(tmp_df.sum())
"""
Action       303.0
Adventure    259.0
Animation     49.0
Biography     81.0
Comedy       279.0
Crime        150.0
Drama        513.0
Family        51.0
Fantasy      101.0
History       29.0
Horror       119.0
Music         16.0
Musical        5.0
Mystery      106.0
Romance      141.0
Sci-Fi       120.0
Sport         18.0
Thriller     195.0
War           13.0
Western        7.0
dtype: float64
"""

print(tmp_df.sum().sort_values())  # 排序显示
"""
Musical        5.0
Western        7.0
War           13.0
Music         16.0
Sport         18.0
History       29.0
Animation     49.0
Family        51.0
Biography     81.0
Fantasy      101.0
Mystery      106.0
Horror       119.0
Sci-Fi       120.0
Romance      141.0
Crime        150.0
Thriller     195.0
Adventure    259.0
Comedy       279.0
Action       303.0
Drama        513.0
dtype: float64
"""
  1. 求和、绘图
# 3. 求和、绘图

# 3.1 求和
res = tmp_df.sum().sort_values(ascending=False)  # 降序排序
print(type(res))  # 
print(res)
"""
Drama        513.0
Action       303.0
Comedy       279.0
Adventure    259.0
Thriller     195.0
Crime        150.0
Romance      141.0
Sci-Fi       120.0
Horror       119.0
Mystery      106.0
Fantasy      101.0
Biography     81.0
Family        51.0
Animation     49.0
History       29.0
Sport         18.0
Music         16.0
War           13.0
Western        7.0
Musical        5.0
dtype: float64
"""

# 3.2 画图
res.plot(kind="bar", figsize=(20, 8), fontsize=14)
plt.xlabel("电影分类", fontsize=14)
plt.ylabel("分类出现的次数", fontsize=14)
plt.title("电影分类柱状图", fontsize=18)
plt.show()

[学习笔记] 1. 机器学习前置知识_第55张图片

因为有好几列的数据,因此不适合画直方图,用柱状图就可以很好的展示数据了。

你可能感兴趣的:(学习笔记,Python,机器学习,机器学习,python)