# 机器学习基础~06.数据变换


## 简单的函数变换

常见的函数变换有平方、开方、取对数、差分等。

$$
x'\ =\ x^2
$$

$$
x'\ =\ \sqrt{x}
$$

$$
x'\ =\ \log(x)
$$

$$
\nabla f(x_k)\ =\ f(x_{k+1})-f(x_k)
$$

简单的函数变换常用来将不具有正态分布的数据变换成具有正态分布的数据；

在时间序列分析中，有时简单的对数变换或者差分运算就可以将非平稳序列转换成平稳序列。 

## 规范化

不同评价指标往往具有不同的量纲，数值间的差别可能很大，不进行处理可能会影响到数据分析的结果。

为了消除指标之间的**量纲和取值范围**差异的影响，需要进行规范化处理，将数据按照比例进行缩放，使之落入一个**特定的区域**，便于进行综合分析。

- **标准化（Standardization）** ：标准化也称为 Z-score 标准化，通过将数据的均值转化为 0，标准差转化为 1，使数据服从标准正态分布。标准化适用于特征的分布不符合正态分布的情况。	

- **归一化（Normalization）** ：归一化将数据范围缩放到 [0, 1] 区间内。这通常是通过对原始值减去最小值并除以范围来实现的。归一化适用于需要确保数据的比例关系不受影响的情况。

- **Softmax变换（Softmax Transformation）** ：Softmax 变换通常用于多类别分类问题中，将原始分数转换为概率分布。它将分数转换为归一化的概率，以便可以预测每个类别的概率。

```python
import numpy as np
from sklearn.preprocessing import StandardScaler, MinMaxScaler
from scipy.special import softmax

# 示例数据
data = np.array([[1, 2, 3],
                 [4, 5, 6],
                 [7, 8, 9]])

# 标准化（Z-score标准化）
scaler = StandardScaler()
data_standardized = scaler.fit_transform(data)

# 归一化（MinMax缩放）
minmax_scaler = MinMaxScaler()
data_normalized = minmax_scaler.fit_transform(data)

# Softmax变换
data_softmax = softmax(data, axis=1)

print("Original Data:\n", data)
print("Standardized Data:\n", data_standardized)
print("Normalized Data:\n", data_normalized)
print("Softmax Transformed Data:\n", data_softmax)
```

- **分组归一化**：在归一化之前先进行分组，对每一组分别进行归一化，适用于组间差异非常大的情形。

```python
grouped = df.groupby('Areas')
df3 = pd.DataFrame()
for name,group in grouped:
    df2 = group[['Age','Salary']]
    group[['Age','Salary']] = (df2-df2.min())/(df2.max()-df2.min())
    df3 = pd.concat([df3,group])
df3.sort_index()
```

## 类型变换

>[!summary]  摘要
>有时由于数据存储类型错误、未正确读取、数据缺失的情况，数据类型并非我们希望的类型，又或者我们需要对数据类型进行变换，符合我们的需求，这时候需要用到「类型变换」。

### 转换为数字类型

比如，某些数字类型的数据可能被错误保存成字符串，可以使用如下方式转为数字类型：

```python
# apply 是对某一行或者某一列进行计算，有axis参数
# applymap 是对所有的元素进行计算
df[['Age','Salary']] = df[['Age','Salary']].apply(pd.to_numeric)
```

### 字符串转换为时间类型

默认情况下，`pd.to_datetime` 将尝试识别以下日期时间格式：

1. ISO 8601 格式，包括日期和时间，例如：`2021-09-13 12:30:45` 或 `2021-09-13T12:30:45`。
2. 日期格式，例如：`2021-09-13`。
3. 时间格式，例如：`12:30:45`。
4. 其他常见日期时间格式，例如：`09/13/2021` 或 `13-Sep-2021`。

```python
import pandas as pd
from datetime import datetime

# 使用 strptime 转换为日期类型
df['Date'] = df['Date'].apply(lambda x:datetime.strptime(x,'%d/%m/%Y').date())

# 使用 pd.to_datetime 转换为日期类型
df['日期']= pd.to_datetime(df['日期'],format='%d/%m/%Y')
```

### 日期转换为字符串类型

日期格式可以按照设定方式格式化为字符串，常见的日期时间格式代码及其含义如下：

- `%Y`: 四位年份（例如，2021）。
- `%y`: 两位年份（例如，21，通常用于表示年份的最后两位）。
- `%m`: 月份（01-12）。
- `%d`: 日期（01-31）。
- `%H`: 24小时制的小时（00-23）。
- `%I`: 12小时制的小时（01-12）。
- `%M`: 分钟（00-59）。
- `%S`: 秒（00-59）。
- `%f`: 微秒（000000-999999）。
- `%j`: 年份中的天数（001-366）。
- `%U`: 年份中的星期数，以星期日为一周的开始（00-53）。
- `%W`: 年份中的星期数，以星期一为一周的开始（00-53）。
- `%c`: 日期时间的本地表示（例如，Tue Sep 13 12:30:45 2021）。
- `%x`: 本地日期表示（例如，09/13/21，根据本地设置）。
- `%X`: 本地时间表示（例如，12:30:45，根据本地设置）。
- `%a`: 缩写的星期几名称（例如，Mon，Tue）。
- `%A`: 完整的星期几名称（例如，Monday，Tuesday）。
- `%b`: 缩写的月份名称（例如，Jan，Feb）。
- `%B`: 完整的月份名称（例如，January，February）。
- `%p`: AM/PM（仅适用于12小时制，例如，AM，PM）。
- `%z`: 时区的偏移量（例如，+0530）。
- `%Z`: 时区的名称或缩写（例如，UTC，EST）。
- `%W`: 年份中的星期数，以星期一为一周的开始（00-53）。

```python
# 使用 strftime 转换为字符串类型
test["Date"] = test["Date"].apply(lambda x:x.strftime("%Y/%m/%d"))
```

### 分箱-连续转换为离散

**数据分箱（Data Binning）** 将连续变量分组为一系列“箱”或“区间”，以便于分析。

![](https://img.papergate.top:5000/i/2025/05/68286cf31deaa.webp)

#### 等宽分箱

**等宽分箱**：是将数据划分为固定大小的区间。这通常需要指定区间的数量或宽度。等宽分箱能够快速划分数据，并且适用于数值分布均匀的数据。

![](https://img.papergate.top:5000/i/2025/05/68286d1da3b9f.webp)

```python
import pandas as pd
#将数据num1按照节点0，30，60，90，100分为5组
bins = [0,30,60,90,100]
num = [3,60,50,43,70,52,26,37,0,80,56,77,35,67,100]
label = ['组1','组2','组3','组4']
cat = pd.cut(num,bins,labels = label)

cat.value_counts()
```

#### 等频分箱

**等频分箱**：是将数据划分为固定数量的区间，每个区间包含相同数量的数据点。这个方法能够在处理数据分布不均匀的情况下，避免某些区间的数据点过多或过少的问题。

![](https://img.papergate.top:5000/i/2025/05/68286d2bd8128.webp)

```python
#将数据num1划分为5组，使每组的元素数量相同
label = ['组1','组2','组3','组4','组5']
cat2 = pd.qcut(num1,5)
cat2

cat2.value_counts()
```

#### 聚类分箱

**聚类分箱**：是一种基于聚类算法的分箱方法，它能够自动将数据分为若干组，每组内的数据点相似度较高。这个方法适用于数据分布不均匀或数据自身不具有明显的规律性的情况。

```python
import numpy as np
from sklearn.cluster import KMeans

# 创建示例数据
data = np.array([1, 2, 2.5, 3, 5, 6, 7, 9, 10, 11, 15, 16, 20]).reshape(-1, 1)

# 设置要分的箱数
n_bins = 3

# 使用K均值聚类进行分箱
kmeans = KMeans(n_clusters=n_bins, random_state=0).fit(data)
labels = kmeans.labels_
cluster_centers = kmeans.cluster_centers_

# 打印每个数据点所属的箱
for i, label in enumerate(labels):
    print(f"Data point {data[i][0]} belongs to bin {label + 1}")

# 打印每个箱的中心点
for bin_num, center in enumerate(cluster_centers):
    print(f"Bin {bin_num + 1} center: {center[0]}")
```

### 编码-离散转换为连续

**编码（Encoding）** 是将**非数值型数据**（如文字、类别、符号）转换为**数值型数据**的过程，以便计算机能够理解和处理。在机器学习和数据分析中，大多数算法（如回归、神经网络）只能处理数字，因此需要对文本、类别等数据进行编码转换。

#### 数值编码 (Label Encoding)

数值编码给每个类别分配一个唯一的数字，如下：

```python
from sklearn.preprocessing import LabelEncoder

# 创建一个 LabelEncoder 对象
label_encoder = LabelEncoder()

# 指定需要编码的列名列表
columns_to_encode = ['Color', 'Size', 'Shape']

# 对指定列进行编码
df_encoded = df.copy()
df_encoded[columns_to_encode] = df_encoded[columns_to_encode].apply(lambda col: label_encoder.fit_transform(col))

# 训练模型（以随机森林分类器为例）
X = df_encoded[columns_to_encode]
y = df_encoded['Label']
model = RandomForestClassifier()
model.fit(X, y)

# 对测试数据进行编码
test_data_encoded[columns_to_encode] = test_data_encoded[columns_to_encode].apply(lambda col: label_encoder.transform(col))

# 使用模型进行预测
predicted_labels = model.predict(test_data_encoded)

# 输出预测结果
print("Predicted Labels:", predicted_labels)
```

#### 独热编码

独热编码每个类别变成一列，用 `0` 或 `1` 表示是否存在，将类别转换为二进制向量。

```python
# get_dummies
pd.get_dummies(data['线路名称'].unique())

# OneHotEncoder
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder()
data['线路名称'] = encoder.fit_transform(data['线路名称'])
```

## 属性构造

通过**已有属性**通过一定的计算生成**新属性**，增强数据表达能力。

### 时间处理

从数据中提取出部分时间信息，比如季节信息或者小时信息。

```python
import pandas as pd
from datetime import datetime

# string 类型的属性新增小时
df['hour'] = df['Time'].apply(lambda x: datetime.strptime(x, '%H:%M:%S').hour)
# 或者
df['hour'] = df['Time'].str.split(':',expand=True)[0]

# datetime 类型的属性新增季节
df['季度']=df['日期'].apply(lambda x: x.quarter)
# 或者
df['季度']=df['日期'].dt.quarter
```

### 分组 Groupby

**分组函数**：将数据按照某个或某些字段进行分组，后续衔接的函数将以组为单位在组内进行运算。

```python
#导入包
import pandas as pd
import numpy as np
#加载数据
data = pd.read_csv( 'data.csv')
#按用户编号分组，计算最大缴费金额
result = data.groupby( '用户编号')[['缴费金额']].max()
#按用户编号分组，计算最小缴费金额
result = data.groupby( '用户编号')[['缴费金额']].min()
#按用户编号分组，计算平均缴费金额
result = data.groupby( '用户编号')[['缴费金额']].mean()
```

### 聚合 Agg

**聚合函数**：将数据按照某个或某些字段进行分组，在组内分别计算某些统计量，如下：

- `'mean'`: 列的均值。
- `'median'`: 列的中位数。
- `'var'`: 列的方差。
- `'std'`: 列的标准差。
- `'skew'`: 列的偏度（Skewness）。
- `'kurt'`: 列的峰度（Kurtosis）。
- `'sum'`: 列的总和。
- `'min'`: 列的最小值。
- `'max'`: 列的最大值。
- `'count'`: 列的非缺失值数量。
- `'mad'`: 列的绝对平均偏差（Mean Absolute Deviation）。
- `'sem'`: 列的标准误差（Standard Error of the Mean）。
- `'prod'`: 列的积。
- `'cumsum'`: 列的累积和。
- `'cumprod'`: 列的累积积。

```python
df= pd.read_csv( 'data.csv ')

result = df.groupby('户号')['电量'].agg(['sum','mean','max','min'])
result.columns=['总电量','平均电量','最大电量','最小电量']
result.index.names=['用户']
```

### 重塑和透视

**数据重塑 (reshaping)** ：是指改变数据的行和列的排列方式。

**数据透视 (pivoting)** ：是指通过旋转数据的行和列，以重新排列数据，并根据指定的聚合函数来生成新的数据。

**长格式**：类似流水账，每一行代表一个观察值，比如某个学生某科目期中考试成绩。

**宽格式**：更像是“矩阵”，每一行代表一个特定观察条件，比如某个特定学生的学号。此外，宽格式数据的列用于表示不同的特征或维度，比如特定科目。

![](https://img.papergate.top:5000/i/2025/05/682595ff374f7.webp)

#### Stack

`stack() `函数用于将数据从宽格式转换为长格式。

![](https://img.papergate.top:5000/i/2025/05/68259213c03ab.webp)

```python
df.stack(level='Subject').reset_index().rename(columns={0: 'Final'})
```

#### Unstack

`unstack() `函数用于将数据从长格式转换为宽格式。

![](https://img.papergate.top:5000/i/2025/05/6825921bee5eb.webp)

```python
df.unstack('Class')
```

#### Melt

`melt() `将原始数据中的多列合并为一列，并根据其他列的值对新列进行重复。可以理解为`stack()`的一种泛化形式。

- 指定`id_vars`参数，表示保持不变的列
- 指定`value_vars`参数，表示需要被转换的列

```python
melted_df = df.melt(id_vars='Student ID',var_name='Subject',value_vars=['Art','Math','Science'],value_name='Score')
```

#### Pivot

`pivot()`可以理解为一种长格式转换为宽格式的特殊情况。

- 指定`index`，表示新 DataFrame 的行索引
- 指定`columns`，表示新 DataFrame 的列索引
- 指定`values`，表示新 DataFrame 的填充数据的值

![](https://img.papergate.top:5000/i/2025/05/6825922b2340b.webp)

```python
df.pivot(index='Student ID',columns='Subject',values='Midterm')
```

我们可以用`pivot_table()`完成一样的操作，和`pivot()`不同的是，`pivot_table()`可以不用指定`columns`（也可以指定，此时同`pivot()`）。

![](https://img.papergate.top:5000/i/2025/05/6825922d21c5a.webp)

```python
df.pivot_table(index=['Subject', 'Student ID'],values=['Midterm','Final'])
```


---

> 作者: Aphros  
> URL: https://blog.papergate.top/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/  

