2024Mathorcup数据集处理与分析
2024年Mathorcup数学应用挑战赛包含四个问题:问题1,2是一个货量预测;问题3,4是人员排班。官方给了四个数据集,分别是附件1,2,3,4.
这篇博文为读者提供对附件1.csv文件的处理和分析过程,同时给出对应的数据集处理程序和讲解。读者可以使用官方提供的下载数据集地址,也可以通过我在文末附上的Gitee仓库查看源代码并下载数据集。提示:官方给的数据集可能编码格式是GBK,不是通常我们使用的UTF-8。(我在仓库中提供的数据集编码格式已修改为UTF-8)
一、对数据集的初步分析
1.1 问题背景
电商物流网络在订单履约中由多个环节组成,下图是一个简化的物流示网络意图,其中分拣中心作为网络的核心机构,需要将把不同流向的包裹分拣并发往下一个目的地,最终到达消费者手中。因此,分拣中心管理效率的提升对整个网络的履约效率和运作成本都起着非常重要的作用。
1.2 数据集处理
为了易于分析,我们需要对数据集进行转换。首先观察原数据集附件1.csv中的内容:
它里面有三个要素:分拣中心名称、日期、货量。其中分拣中心命名规则“SC”+数字编号,我们注意到数字编号并不是从1按自然数到57(因为一共存在57个分拣中心)。第一列,我们可以按照分拣中心的数字编号对其重新排列,对于日期我们按照时间先后顺序进行重新排列。
分析:
为了实现上述目的,我们需要用到Pandas库,它可以读取数据集并将其作为一个DataFrame类型的对象进行存储,同时提供了丰富的操作方法。这里有两种处理思路,同时得到了两个数据表:附件1数据排序.csv,附件1(pivot_table).csv
对于数据表“附件1数据排序.csv”,它的表现形式与原数据集相同,同样是 6954 × 3 6954\times3 6954×3,这里 6954 = 57 × 122 6954=57\times122 6954=57×122其中57代表分拣中心的数量,122代表对于每一个分拣中心的日期记录数量,它代表从8月1日一直到11月30日,即 122 = ( 31 + 30 ) × 2 122=(31+30)\times2 122=(31+30)×2。
对于数据表“附件1(pivot_table).csv”,它的列表示各分拣中心,行代表日期。数据维度: 122 × 57 122\times57 122×57.我这里将数据表部分截图展示出来.
print(df['分拣中心'].nunique())
>>57
print(df['日期'].nunique())
>>122
二、数据集处理与转换
2.1 程序结构
定义了四个函数:dataSort,dataPivot,createFolder,main
作用:前面三个里面,dataSort用于生成“附件1数据排序.csv”,dataPivot则实现生成“附件1(pivot_table).csv”,而createFolder函数为了创建一个文件夹(便于管理),最后使用main函数综合以上项目路逻辑。
- dataSort函数
def dataSort(datafile,outputFile):
"""
对数据集按照日期和列名中数字编号大小进行排序,
并将结果保存为csv文件,同时返回一个dataframe变量
params:
datafile:数据集文件路径
outputFile:生成文件的保存目录
return:
DataFrame:排序后的数据
"""
OUTFILE = '附件1数据排序.csv'
df = pd.read_csv(datafile,encoding='utf-8')
df['Number'] = df['分拣中心'].str.extract('(\d+)').astype(int)
df['日期'] = pd.to_datetime(df['日期'])
df_sorted = df.sort_values(by=['Number','日期'])
df_sorted.drop(columns='Number',inplace=True)
df_sorted.to_csv(outputFile+OUTFILE,index=False)
return df_sorted
- dataPivot函数
def dataPivot(data, outputFile):
PIVOTFILE = '附件1(pivot_table).csv'
data_args = data.columns
df = pd.pivot_table(data, values=data_args[2], columns=data_args[0], index=data_args[1], sort=False)
# 提取列名中的数字部分并排序
sorted_columns = sorted(df.columns, key=lambda x: int(re.search(r'\d+', x).group()))
df = df[sorted_columns]
df.to_csv(outputFile + PIVOTFILE, index=True)
2.2 特别强调:
- 文件路径
- 分拣中心列名排序逻辑
- 使用Pivot_table方法时的排序逻辑
程序中文件路径应该替换为自己在电脑上文件保存的位置,对于分拣中心列名的排序,在两个函数对应的处理中使用了不同的逻辑。
dataSort处理方式是将每一个分拣中心列名里的数字编号提取出来,单独构成一个新列“Number”。排序逻辑采用sort_values方法如下:
df['Number'] = df['分拣中心'].str.extract('(\d+)').astype(int)
df['日期'] = pd.to_datetime(df['日期'])
df_sorted = df.sort_values(by=['Number','日期'])
dataPivot处理:若仅依靠pivot_table参数中的sort=True,那么它的排序逻辑和我们预期的不一样(各位朋友可以尝试一下),具体它会显示:SC1,SC10,……。而我们预期是SC1,SC2……因此我们使用sorted函数进行排序,而这个函数我之前使用的时候没有想到它还能应对更为复杂的排序情况。
df = pd.pivot_table(data, values=data_args[2], columns=data_args[0], index=data_args[1], sort=False)
# 提取列名中的数字部分并排序
sorted_columns = sorted(df.columns, key=lambda x: int(re.search(r'\d+', x).group()))
df = df[sorted_columns]
sorted参数里有一个key,它可以按照用户定义的方式来进行排序。
2.3 sorted函数浅述
为了深入了解sorted函数,我查看了它内部的定义,这里为方便大家理解,我列举出该函数的定义和声明:
## sorted函数的定义
@overload
def sorted(
iterable: Iterable[SupportsRichComparisonT], /, *, key: None = None, reverse: bool = False
) -> list[SupportsRichComparisonT]: ...
@overload
def sorted(iterable: Iterable[_T], /, *, key: Callable[[_T], SupportsRichComparison], reverse: bool = False) -> list[_T]: ...
总之,我第一眼看上去也感到比较奇怪,这是因为我对装饰器的用法和定义不熟悉,这里用到一个重载装饰器:@overload。下面来对里面一些奇怪的类型说明做一个解释:
-
@overload
装饰器:@overload
装饰器用于定义函数的多个重载版本。这些重载版本不会实际执行,而是用于类型检查器(如mypy
)来推断函数的返回类型。
-
sorted参数
- iterable: Iterable[SupportsRichComparisonT]:一个可迭代对象,其元素支持富比较(即实现了所有比较运算符,如 <, <=, >, >=, ==, !=)。
- /:表示位置参数,必须在位置参数位置传递。
- key: None = None:关键字参数,默认为 None。
- reverse: bool = False:关键字参数,表示是否反转排序,默认为 False。
- 返回类型:list[SupportsRichComparisonT]:返回一个列表,列表元素类型与输入的可迭代对象元素类型相同。
-
示例说明
为了方便读者了解,我这里给出一个关于重载的示例:
from typing import overload, List, Union, Callable
# 定义重载版本
@overload
def process(data: List[int]) -> int: ...
@overload
def process(data: List[str]) -> str: ...
@overload
def process(data: List[float], key: Callable[[float], float]) -> float: ...
# 实现函数
def process(data: Union[List[int], List[str], List[float]], key: Callable[[float], float] = None) -> Union[int, str, float]:
if all(isinstance(item, int) for item in data):
return sum(data)
elif all(isinstance(item, str) for item in data):
return ''.join(data)
elif all(isinstance(item, float) for item in data) and key is not None:
return sum(key(item) for item in data)
else:
raise ValueError("Invalid input")
# 使用示例
print(process([1, 2, 3])) # 输出: 6
print(process(["a", "b", "c"])) # 输出: "abc"
print(process([1.1, 2.2, 3.3], key=lambda x: x * 2)) # 输出: 13.2
实现了一个 process 函数,接受一个联合类型 Union[List[int], List[str], List[float]] 和一个可选的 key 函数。
- 根据输入数据的类型执行不同的逻辑:
- 如果所有元素都是整数,返回它们的和。
- 如果所有元素都是字符串,返回它们的连接结果。
- 如果所有元素都是浮点数,并且提供了 key 函数,返回应用 key 函数后的和
三、完整程序
- 导入必要库
import pandas as pd
import os
import re
- 功能函数
def dataSort(datafile,outputFile):
"""
对数据集按照日期和列名中数字编号大小进行排序,
并将结果保存为csv文件,同时返回一个dataframe变量
params:
datafile:数据集文件路径
outputFile:生成文件的保存目录
return:
DataFrame:排序后的数据
"""
OUTFILE = '附件1数据排序.csv'
df = pd.read_csv(datafile,encoding='utf-8')
df['Number'] = df['分拣中心'].str.extract('(\d+)').astype(int)
df['日期'] = pd.to_datetime(df['日期'])
df_sorted = df.sort_values(by=['Number','日期'])
df_sorted.drop(columns='Number',inplace=True)
df_sorted.to_csv(outputFile+OUTFILE,index=False)
return df_sorted
# def dataPivot(data,outputFile):
# PIVOTFILE = '附件1(pivot_table).csv'
# data_args = data.columns
# df = pd.pivot_table(data,values=data_args[2],columns=data_args[0],\
# index=data_args[1],sort=False)
# df.to_csv(outputFile+PIVOTFILE,index=False)
def dataPivot(data, outputFile):
PIVOTFILE = '附件1(pivot_table).csv'
data_args = data.columns
df = pd.pivot_table(data, values=data_args[2], columns=data_args[0], index=data_args[1], sort=False)
# 提取列名中的数字部分并排序
sorted_columns = sorted(df.columns, key=lambda x: int(re.search(r'\d+', x).group()))
df = df[sorted_columns]
df.to_csv(outputFile + PIVOTFILE, index=True)
def createFolder(diractory,folder_name):
"""
在指定目录下创建文件夹,并返回文件夹的完整路径。
param:
directory (str): 父目录的路径。
folder_name (str): 要创建的文件夹名称。
return:
str: 新创建文件夹的完整路径。
"""
folder_path = os.path.join(diractory,folder_name)
if not os.path.exists(folder_path):
os.makedirs(folder_path)
print(f'原文件目录不存在,创建:{folder_path}')
else:
print(f'原文件目录已经存在,如下:{folder_path}')
return folder_path+'/'
- main()
def main():
GEN_FOLDER ='2024MC数据处理'
datafile_path = './weblog/Data/2024Mathorcup数学应用挑战赛/附件/附件1.csv'
outputFile = '.\weblog\Output'
folder_generate = createFolder(outputFile,GEN_FOLDER)
data_sorted = dataSort(datafile_path,folder_generate)
dataPivot(data_sorted,folder_generate)
- 运行
if __name__ == '__main__':
main()
以上程序和代码,以及数据均可以在Gitee仓库中找到:溪海莘:Study、Practice、Share