刚刚打完数模比赛,特此记录一下我在负责数据清理时解决问题的方法。
当我们在进行数据分析和数据清洗时,可以对数据按分钟、小时、天、周、月、年进行划分。下文讲叙述划分数据的主要思路。我认为最精华的部分当属按周划分。
我们得到一批数据,首先观察其时间结构。
import pandas as pd
import numpy as np
fpath = './CSVInFile/DE.csv'
data_set = pd.read_csv(fpath)
print(data_set.info())
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 50401 entries, 0 to 50400
# Data columns (total 6 columns):
# # Column Non-Null Count Dtype
# --- ------ -------------- -----
# 0 cet_cest_timestamp 50401 non-null object
# 1 DE_load_actual_entsoe_transparency 50401 non-null int64
# 2 DE_solar_generation_actual 50401 non-null int64
# 3 DE_wind_generation_actual 50401 non-null int64
# 4 DE_wind_offshore_generation_actual 50401 non-null int64
# 5 DE_wind_onshore_generation_actual 50401 non-null int64
# dtypes: int64(5), object(1)
# memory usage: 2.3+ MB
# None
print(data_set.iloc[:, 0])
# 0 2015-01-01T00:00:00+0100
# 1 2015-01-01T01:00:00+0100
# 2 2015-01-01T02:00:00+0100
# 3 2015-01-01T03:00:00+0100
# 4 2015-01-01T04:00:00+0100
# ...
# 50396 2020-09-30T21:00:00+0200
# 50397 2020-09-30T22:00:00+0200
# 50398 2020-09-30T23:00:00+0200
# 50399 2020-10-01T00:00:00+0200
# 50400 2020-10-01T01:00:00+0200
# Name: cet_cest_timestamp, Length: 50401, dtype: object
数据按日划分
按日划分是按周划分的基础。发现2015-01-01T00:00:00+0100
的后5位对数据的分析没有帮助,直接使用切片法截断后面部分即可。需要讲数据按日划分即将同一天的数据划分到同一组中,也就是说通过把时间裁剪成YYYY-mm-dd
的样式,再使用groupby()
函数进行分组就能够实现这个功能。
函数代码如下:
# 去掉时间后缀(以天为单位)
def Replace_Day(data_set):
for i in range(len(data_set)):
data_str = data_set.loc[i, 'cet_cest_timestamp']
data_set.loc[i, 'cet_cest_timestamp'] = data_str[0: 10] # 切片法切割字符串,得到[0,10)区间的元素
return data_set
# 将数据以天作和(发电总和)
def Sum_perDay_sum(data_set):
# 将str型的时间数据转为DateTimeIndex型,并且规定时间样式为YYYY-mm-dd
data_set['cet_cest_timestamp'] = data_set['cet_cest_timestamp'].apply(lambda x: datetime.strptime(str(x)[0:20], '%Y-%m-%d'))
# 使用groupby()函数对数据进行分组并求和
data_set = data_set.groupby('cet_cest_timestamp').sum()
for i in range(len(data_set.columns)):
# 对每个元素四舍五入保留两位小数
data_set[data_set.columns[i]] = round(data_set[data_set.columns[i]], 2)
return data_set
# 将数据以天作平均(load)
def Sum_perDay_mean(data_set):
# 将str型的时间数据转为DateTimeIndex型,并且规定时间样式为YYYY-mm-dd
data_set['cet_cest_timestamp'] = data_set['cet_cest_timestamp'].apply(lambda x: datetime.strptime(str(x)[0:20], '%Y-%m-%d'))
# 使用groupby()函数对数据进行分组并求平均
data_set = data_set.groupby('cet_cest_timestamp').mean()
for i in range(len(data_set.columns)):
# 对每个元素四舍五入保留两位小数
data_set[data_set.columns[i]] = round(data_set[data_set.columns[i]], 2)
return data_set
在代码中调用即可
data_set = Replace_Day(data_set)
data_set = Sum_perDay_sum(data_set)
# data_set = Sum_perDay_mean(data_set)
print(data_set.iloc[:, 0])
# cet_cest_timestamp
# 2015-01-01 1092227
# 2015-01-02 1248548
# 2015-01-03 1115212
# 2015-01-04 1082128
# 2015-01-05 1321261
# ...
# 2020-09-27 1083709
# 2020-09-28 1349495
# 2020-09-29 1387775
# 2020-09-30 1385747
# 2020-10-01 92347
# Name: DE_load_actual_entsoe_transparency, Length: 2101, dtype: int64
数据按月划分
按月划分也是同理,只需要切割字符串,同时修改日期样式为YYYY-mm
即可。
代码如下:
# 去掉时间后缀(以月为单位)
def Replace_Month(data_set):
for i in range(len(data_set)):
data_str = data_set.loc[i, 'cet_cest_timestamp']
data_set.loc[i, 'cet_cest_timestamp'] = data_str[0: 7] # 切片法切割字符串,得到[0,7)区间的元素
return data_set
# 将数据以月作和(发电总和)
def Sum_perMonth_sum(data_set):
# 将str型的时间数据转为DateTimeIndex型,并且规定时间样式为YYYY-mm
data_set['cet_cest_timestamp'] = data_set['cet_cest_timestamp'].apply(lambda x: datetime.strptime(str(x)[0:20], '%Y-%m'))
# 使用groupby()函数对数据进行分组并求和
data_set = data_set.groupby('cet_cest_timestamp').sum()
for i in range(len(data_set.columns)):
# 对每个元素四舍五入保留两位小数
data_set[data_set.columns[i]] = round(data_set[data_set.columns[i]], 2)
return data_set
# 将数据以月作平均(load)
def Sum_perMonth_mean(data_set):
# 将str型的时间数据转为DateTimeIndex型,并且规定时间样式为YYYY-mm
data_set['cet_cest_timestamp'] = data_set['cet_cest_timestamp'].apply(lambda x: datetime.strptime(str(x)[0:20], '%Y-%m'))
# 使用groupby()函数对数据进行分组并求平均
data_set = data_set.groupby('cet_cest_timestamp').mean()
for i in range(len(data_set.columns)):
# 对每个元素四舍五入保留两位小数
data_set[data_set.columns[i]] = round(data_set[data_set.columns[i]], 2)
return data_set
直接调用
data_set = Replace_Month(data_set)
data_set = Sum_perMonth_sum(data_set)
# data_set = Sum_perMonth_mean(data_set)
print(data_set.iloc[:, 0])
# cet_cest_timestamp
# 2015-01-01 42224613
# 2015-02-01 39257398
# 2015-03-01 42276357
# 2015-04-01 37956450
# 2015-05-01 37723133
# ...
# 2020-06-01 35727188
# 2020-07-01 38042366
# 2020-08-01 37795497
# 2020-09-01 37897222
# 2020-10-01 92347
# Name: DE_load_actual_entsoe_transparency, Length: 70, dtype: int64
数据按年划分
按年划分也是同理啦,直接附代码:
# 去掉时间后缀(以年为单位)
def Replace_Year(data_set):
for i in range(len(data_set)):
data_str = data_set.loc[i, 'cet_cest_timestamp']
data_set.loc[i, 'cet_cest_timestamp'] = data_str[0: 4] # 切片法切割字符串,得到[0,4)区间的元素
return data_set
# 将数据以年作和
def Sum_perYear_sum(data_set):
# 将str型的时间数据转为DateTimeIndex型,并且规定时间样式为YYYY
data_set['cet_cest_timestamp'] = data_set['cet_cest_timestamp'].apply(lambda x: datetime.strptime(str(x)[0:20], '%Y'))
# 使用groupby()函数对数据进行分组并求和
data_set = data_set.groupby('cet_cest_timestamp').sum()
for i in range(len(data_set.columns)):
# 对每个元素四舍五入保留两位小数
data_set[data_set.columns[i]] = round(data_set[data_set.columns[i]], 2)
return data_set
# 将数据以年作平均
def Sum_perYear_mean(data_set):
# 将str型的时间数据转为DateTimeIndex型,并且规定时间样式为YYYY
data_set['cet_cest_timestamp'] = data_set['cet_cest_timestamp'].apply(lambda x: datetime.strptime(str(x)[0:20], '%Y'))
# 使用groupby()函数对数据进行分组并求和
data_set = data_set.groupby('cet_cest_timestamp').mean()
for i in range(len(data_set.columns)):
# 对每个元素四舍五入保留两位小数
data_set[data_set.columns[i]] = round(data_set[data_set.columns[i]], 2)
return data_set
调用:
data_set = Replace_Year(data_set)
data_set = Sum_perYear_sum(data_set)
# data_set = Sum_perYear_mean(data_set)
print(data_set.iloc[:, 0])
# cet_cest_timestamp
# 2015-01-01 479500476
# 2016-01-01 486992473
# 2017-01-01 492119683
# 2018-01-01 498893806
# 2019-01-01 490475307
# 2020-01-01 348885004
# Name: DE_load_actual_entsoe_transparency, dtype: int64
数据按周划分
当时在网上查阅了很多资料都没有找到能够将数据按周划分的小寄巧,因此联想到可以用groupby()
函数来尝试按照自己的需求来划分数据。
通过在数据集中附加一列weekline,weekline的值从1递增至data_set的表长,也就是给每一行的元素配置一个索引。突然,灵光一现,我想起了这个函数:
from math import ceil
ceil(weekline_index / 7)
什么意思呢?我认为首先将元素按日进行划分,然后再添加一列weekline索引。不难发现,如果对weekline的每一个元素weekline_index都执行一遍
weekline_index = ceil(weekline_index / 7)
那么第一周所有元素在weekline列的值都为1,第二周的都为2,…,如此划分,再调用
data_set = data_set.groupby('weekline')
不就能实现数据的按周划分吗?!
下面附一波代码:
注意需要调用reset_index
函数来重置索引,否则会因索引不一致导致出现NAN值。
def Sum_perWeek(data_perDay):
# 生成一列周索引,1-表长
weekline = pd.DataFrame(np.arange(1, len(data_perDay) + 1).reshape(len(data_perDay), 1))
# 需要重置索引,否则会因索引不一致导致出现NAN值
data_perDay = data_perDay.reset_index(drop=True)
data_perDay.loc[:, 'weekline'] = weekline
# 对每个元素取ceil(i/7),然后对其按周分组
for i in range(len(data_perDay)):
data_perDay.iloc[i, len(data_perDay.columns) - 1] = ceil(data_perDay.iloc[i, len(data_perDay.columns) - 1] / 7)
# 合并同类项
data_perDay = data_perDay.groupby('weekline').sum()
return data_perDay
调用:
data_set = Replace_Day(data_set)
data_set = Sum_perDay_sum(data_set)
data_set = Sum_perWeek(data_set)
print(data_set.iloc[:, 0])
# weekline
# 1 8630861
# 2 9751449
# 3 9709930
# 4 9904434
# 5 9869768
# ...
# 297 8680845
# 298 8668544
# 299 8810050
# 300 9096997
# 301 92347
# Name: DE_load_actual_entsoe_transparency, Length: 301, dtype: int64
总结
实际上上文的按周划分算法不限定划分的天数,如果需要划分为任意参数,只需要将函数中的7
改为一个特定需求的天数即可。但此函数算法仅仅保证需求实现而并未进行复杂度优化,因此执行效率可能较低。
同为行路人,共勉。