# 数学建模导论 --- pandas的简单使用

--2025年2月学习笔记

学习内容摘自[intro-mathmodel-课程详情 | Datawhale](https://www.datawhale.cn/learn/content/85/3136)

感谢分享！

学习数学建模更重要的是知道面对不同体量的数据如何快速下手找准最合适的建模方法

靠山吃山，靠水吃水。小数据有小数据的方法，大数据有大数据的方法。拿着线性回归的那一套去拟合大数据，往往效果不会很好；拿着神经网络去学小数据，学到的东西往往没有意义（我们会笑称：你是想去研究few-shot learning吗）。面对不同的数据，能够使用最合适的方法最为重要。而判断此方法是否合适，一个根本的衡量就是数据的体量。

## 数据科学的研究对象

- 数据的获取和存储：包括爬虫、软件定义存储、硬件存储有关背景知识等。
- 数据的处理：包括分布式计算、并行计算、数据流等知识，以及Hadoop、Spark等大数据框架。
- 数据的分析：包括统计学、数据挖掘与机器学习、计算机视觉、自然语言处理等内容，重在挖掘数据中的模式与知识。
- 数据的管理：现代数据库系统及其架构等内容。
- 数据的应用：数据可视化、数据相关软件的开发、报表分析以及如何将数据挖掘得到的结果还原为实际问题的解决方案。

## 使用pandas处理数据的基础

### 1.环境相关

这里我使用的是pycharm进行的python编写，环境是用conda建的新环境

python = 3.10

```bash
# 使用清华镜像源下载更快
pip insatll pandas -i https://pypi.tuna.tsinghua,edu.cn/simple
```

### 2.练习一

```python
import pandas as pd
import numpy as np

#  字典格式
data = {'animal': ['cat', 'cat', 'snake', 'dog', 'dog', 'cat', 'snake', 'cat', 'dog', 'dog'],
        'age': [2.5, 3, 0.5, np.nan, 5, 2, 4.5, np.nan, 7, 3],
        'visits': [1, 3, 2, 3, 2, 3, 1, 1, 2, 1],
        'priority': ['yes', 'yes', 'no', 'yes', 'no', 'no', 'no', 'yes', 'no', 'no']}

# 作为索引
labels = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

df = pd.DataFrame(data)

# 默认输出有数值的统计结果
print(df.describe())
#             age     visits
# count  8.000000  10.000000
# mean   3.437500   1.900000
# std    2.007797   0.875595
# min    0.500000   1.000000
# 25%    2.375000   1.000000
# 50%    3.000000   2.000000
# 75%    4.625000   2.750000
# max    7.000000   3.000000
# 计数，均值，标准差，最小值。。。。最大值


# 增加参数即可输出上面的animal和priority
print(df.describe(include='all'))
#        animal       age     visits priority
# count      10  8.000000  10.000000       10
# unique      3       NaN        NaN        2
# top       cat       NaN        NaN       no
# freq        4       NaN        NaN        6
# mean      NaN  3.437500   1.900000      NaN
# std       NaN  2.007797   0.875595      NaN
# min       NaN  0.500000   1.000000      NaN
# 25%       NaN  2.375000   1.000000      NaN
# 50%       NaN  3.000000   2.000000      NaN
# 75%       NaN  4.625000   2.750000      NaN
# max       NaN  7.000000   3.000000      NaN

# 输出为数据的前五列
print(df.head(5))
#   animal  age  visits priority
# 0    cat  2.5       1      yes
# 1    cat  3.0       3      yes
# 2  snake  0.5       2       no
# 3    dog  NaN       3      yes
# 4    dog  5.0       2       no


# 除了最后一列不输出
print(df.head(-1))
#   animal  age  visits priority
# 0    cat  2.5       1      yes
# 1    cat  3.0       3      yes
# 2  snake  0.5       2       no
# 3    dog  NaN       3      yes
# 4    dog  5.0       2       no
# 5    cat  2.0       3       no
# 6  snake  4.5       1       no
# 7    cat  NaN       1      yes
# 8    dog  7.0       2       no

# 打印有动物和age的列
print(df[['animal', 'age']])
#   animal  age
# 0    cat  2.5
# 1    cat  3.0
# 2  snake  0.5
# 3    dog  NaN
# 4    dog  5.0
# 5    cat  2.0
# 6  snake  4.5
# 7    cat  NaN
# 8    dog  7.0
# 9    dog  3.0

# 全部打印
print(df)
#   animal  age  visits priority
# 0    cat  2.5       1      yes
# 1    cat  3.0       3      yes
# 2  snake  0.5       2       no
# 3    dog  NaN       3      yes
# 4    dog  5.0       2       no
# 5    cat  2.0       3       no
# 6  snake  4.5       1       no
# 7    cat  NaN       1      yes
# 8    dog  7.0       2       no
# 9    dog  3.0       1       no

# 输出3 4 8行的 animal age
print(df.loc[[3, 4, 8], ['animal', 'age']])
#   animal  age
# 3    dog  NaN
# 4    dog  5.0
# 8    dog  7.0

# 输出原有index
print(df.index)
# RangeIndex(start=0, stop=10, step=1)

# 更改index
df.index = labels
print(df.index)
# Index(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'], dtype='object')

print('改变之前:', df.loc[['f'],['age']])
df.loc[['f'], ['age']] = 1.5
print('改变之后:', df.loc[['f'],['age']])
# 改变之前：    age
# f  2.0
# 改变之后    age
# f  1.5

# 求和
print(df['visits'].sum())
# 19


print(df.groupby(['animal'])['age'].mean())
# animal
# cat      2.333333
# dog      5.000000
# snake    2.500000
# Name: age, dtype: float64


```

### 3.练习二

```python
import numpy as np
import pandas as pd

df = pd.DataFrame({'From_To': ['LoNDon_paris', 'MAdrid_miLAN', 'londON_StockhOlm', 'Budapest_PaRis', 'Brussels_londOn'],
              'FlightNumber': [10045, np.nan, 10065, np.nan, 10085],
              'RecentDelays': [[23, 47], [], [24, 43, 87], [13], [67, 32]],
                   'Airline': ['KLM(!)', '<Air France> (12)', '(British Airways. )', '12. Air France', '"Swiss Air"']})

# 打印出来看看df
print(df)
#             From_To  FlightNumber  RecentDelays              Airline
# 0      LoNDon_paris       10045.0      [23, 47]               KLM(!)
# 1      MAdrid_miLAN           NaN            []    <Air France> (12)
# 2  londON_StockhOlm       10065.0  [24, 43, 87]  (British Airways. )
# 3    Budapest_PaRis           NaN          [13]       12. Air France
# 4   Brussels_londOn       10085.0      [67, 32]          "Swiss Air"
# 缺失值常用nan表示

# interpolate() 是 Pandas 中的一个方法，用于填补缺失值（NaN）。
# 它使用插值算法推测缺失数据点的值，默认情况下采用线性插值（通过连接已知点的直线来估算未知点的值）
# astype(int) 方法将插值后的 FlightNumber 列的所有数据转换为整数类型。
df['FlightNumber'] = df['FlightNumber'].interpolate().astype(int)
print(df)
#             From_To  FlightNumber  RecentDelays              Airline
# 0      LoNDon_paris         10045      [23, 47]               KLM(!)
# 1      MAdrid_miLAN         10055            []    <Air France> (12)
# 2  londON_StockhOlm         10065  [24, 43, 87]  (British Airways. )
# 3    Budapest_PaRis         10075          [13]       12. Air France
# 4   Brussels_londOn         10085      [67, 32]          "Swiss Air"

temp = df['From_To'].str.split("_", expand=True)
print(temp)
#           0          1
# 0    LoNDon      paris
# 1    MAdrid      miLAN
# 2    londON  StockhOlm
# 3  Budapest      PaRis
# 4  Brussels     londOn

# 赋予为列From与To
temp.columns = ['From', 'To']
print(temp)
#        From         To
# 0    LoNDon      paris
# 1    MAdrid      miLAN
# 2    londON  StockhOlm
# 3  Budapest      PaRis
# 4  Brussels     londOn

# 处理首字母
temp['From'] = temp['From'].str.capitalize()
temp['To'] = temp['To'].str.capitalize()
print(temp)
#        From         To
# 0    London      Paris
# 1    Madrid      Milan
# 2    London  Stockholm
# 3  Budapest      Paris
# 4  Brussels     London


# 'From_To'：
# 这是你想要删除的列名。在这个例子中，删除的是名为 'From_To' 的列。
# axis 参数指定是删除行还是列：
# axis=0 表示删除行（按行操作）。
# axis=1 表示删除列（按列操作）。
# inplace=True 表示直接在原数据框 df 上进行操作，修改会生效并且不会返回新的数据框。即，不需要重新赋值给 df，它会在原地修改数据框。
# 如果设置为 inplace=False（默认值），则 drop() 方法会返回一个删除指定列的新的数据框，而原数据框 df 不会被改变。
df.drop('From_To', axis=1, inplace=True)
print(df)
#    FlightNumber  RecentDelays              Airline
# 0         10045      [23, 47]               KLM(!)
# 1         10055            []    <Air France> (12)
# 2         10065  [24, 43, 87]  (British Airways. )
# 3         10075          [13]       12. Air France
# 4         10085      [67, 32]          "Swiss Air"


# 合并到df
df[['From', 'To']] = temp
print(df)
#    FlightNumber  RecentDelays              Airline      From         To
# 0         10045      [23, 47]               KLM(!)    London      Paris
# 1         10055            []    <Air France> (12)    Madrid      Milan
# 2         10065  [24, 43, 87]  (British Airways. )    London  Stockholm
# 3         10075          [13]       12. Air France  Budapest      Paris
# 4         10085      [67, 32]          "Swiss Air"  Brussels     London

# 去除其他字符
# r'([a-zA-Z\s]+)' 是正则表达式，用于匹配字符串中连续的字母和空格
# strip()去除首位空格，但是中间不管
df['Airline'] = df['Airline'].str.extract(r'([a-zA-Z\s]+)', expand=False).str.strip()
print(df)
#    FlightNumber  RecentDelays             Airline      From         To
# 0         10045      [23, 47]                 KLM    London      Paris
# 1         10055            []          Air France    Madrid      Milan
# 2         10065  [24, 43, 87]  British    Airways    London  Stockholm
# 3         10075          [13]          Air France  Budapest      Paris
# 4         10085      [67, 32]           Swiss Air  Brussels     London


delays = df['RecentDelays'].apply(pd.Series)
print(delays)
#       0     1     2
# 0  23.0  47.0   NaN
# 1   NaN   NaN   NaN
# 2  24.0  43.0  87.0
# 3  13.0   NaN   NaN
# 4  67.0  32.0   NaN
delays.columns = ['delay_%s' % i for i in range(1, len(delays.columns)+1)]
df = df.drop('RecentDelays', axis=1).join(delays, how='left')
# join(delays, how='left')的主体似乎是dalays来区分左右
print(df)
#    FlightNumber          Airline      From  ... delay_1  delay_2  delay_3
# 0         10045              KLM    London  ...    23.0     47.0      NaN
# 1         10055       Air France    Madrid  ...     NaN      NaN      NaN
# 2         10065  British Airways    London  ...    24.0     43.0     87.0
# 3         10075       Air France  Budapest  ...    13.0      NaN      NaN
# 4         10085        Swiss Air  Brussels  ...    67.0     32.0      NaN


# 将delay_i列的控制nan都填为自身的平均值。fillna自如其意
for i in range(1, 4):
    df[f'delay_{i}'] = df[f'delay_{i}'].fillna(np.mean(df[f'delay_{i}']))
print(df)
#    FlightNumber          Airline      From  ... delay_1    delay_2  delay_3
# 0         10045              KLM    London  ...   23.00  47.000000     87.0
# 1         10055       Air France    Madrid  ...   31.75  40.666667     87.0
# 2         10065  British Airways    London  ...   24.00  43.000000     87.0
# 3         10075       Air France  Budapest  ...   13.00  40.666667     87.0
# 4         10085        Swiss Air  Brussels  ...   67.00  32.000000     87.0

# 增加一行相同的
df = df._append(df.loc[df['FlightNumber'] == 10085, :], ignore_index=True)
print(df)
# [5 rows x 7 columns]
#    FlightNumber          Airline      From  ... delay_1    delay_2  delay_3
# 0         10045              KLM    London  ...   23.00  47.000000     87.0
# 1         10055       Air France    Madrid  ...   31.75  40.666667     87.0
# 2         10065  British Airways    London  ...   24.00  43.000000     87.0
# 3         10075       Air France  Budapest  ...   13.00  40.666667     87.0
# 4         10085        Swiss Air  Brussels  ...   67.00  32.000000     87.0
# 5         10085        Swiss Air  Brussels  ...   67.00  32.000000     87.0


# 去重
df = df.drop_duplicates()
print(df)
#    FlightNumber          Airline      From  ... delay_1    delay_2  delay_3
# 0         10045              KLM    London  ...   23.00  47.000000     87.0
# 1         10055       Air France    Madrid  ...   31.75  40.666667     87.0
# 2         10065  British Airways    London  ...   24.00  43.000000     87.0
# 3         10075       Air France  Budapest  ...   13.00  40.666667     87.0
# 4         10085        Swiss Air  Brussels  ...   67.00  32.000000     87.0
```

## 数据的规约

min-max规约的表达式形如：
$$
x_n=\frac {max(x)−min(x)}{x−min(x)}
$$
这一操作的目的是为了消除量纲影响，所有的属性都被规约到[0,1]的范围内，数据的偏差不会那么大。但是如果出现异常值，比如非常大的数值，那么这个数据的分布是有偏的。为了对数据的分布进行规约，还会使用到另一个常用的方法就是Z-score规约：
$$
x_n=\frac {x− \bar x}{std(x)}
$$
本质上，一列数据减去其均值再除以标准差，如果这一列数据近似服从正态分布，这个过程就是化为标准正态分布的过程。Z-score规约和min-max规约往往不是二者取其一，有时候两个可以组合起来用。

## 初探数据分析

### 分析方法

回归分析：

​	对数据进行线性回归，看相关度的系数即可分析

分类分析：

​	逻辑分类法

### 假设检验

​	正态性检验：于参数检验比非参数检验更灵敏，因此一旦数据是正态分布的，我们应该使用参数检验，三种方法判断数据的正态性：可视化判断-正态分布概率图；Shapiro-Wilk检验；D'Agostino's K-squared检验。

画图：分位数一一对应即可

1）单组样本均值假定的检验

​	单样本t检验与wilcoxon检验。

2）两组样本的均值相等性检验

若两个样本的总体都服从正态分布，那么我们可以使用双样本t检验。如果不服从正态分布，则可以使用Mannwhitneyu秩和检验，Mannwhitneyu秩和检验是一种非参数检验。

先进行两组数据的方差齐性检验

若在显著性水平0.05下，不能拒绝两组样本方差相等的假设(p=0.8277)，因此需要使用方差相等的t检验

在进行两组间均值比较的时候，有一种特殊情况——两个样本“故意”不独立。这种情况多出现两个样本分别为同一个受试个体不同时间的受试结果，这两个样本是“成对”的，是彼此紧密相连的。对这样两个样本进行均值比较检验，就是成对检验
