最近学习了处理气象数据NCDC 的过程,在这里记录并分享一下,由于刚开始学相关知识,文中有疏漏和不足,还请多见谅。
4.3更 摸了好久,决定还是更新一下,主要是稍微改了一点点代码,把其他列的数据也处理了,顺便添了一些站点信息,最后附上我处理好的数据。
目录
前言
一、处理步骤
1 获取待处理数据
(1)NCDC数据获取
(2)NCDC数据处理为xlsx格式
2 处理数据过程
二、完整代码
三、代码说明
1.处理CSV格式数据
2.添加经纬度等信息
本文实现内容:对于格式转换后
的NCDN数据(按年份保存的站点xlsx格数数据),将每年的站点数据按年重采样
的各要素数据(气温、降水、气压等),并添加站点列,最按年后保存到一张Excel表中。
下面用图来表示
这里是待处理的数据,包含1990-2020共31个文件夹:
每个文件夹下有几百个当年站点数据的Excel表文件,以1990年的文件为例:
每个站点Excel表中又有几千行记录数据:
接下来是处理后的数据,这些站点数据的几千行记录数据被按年重采样到一行,然后再将每年的几百行站点数据保存到一张表中,最终保存为31个Excel表,以处理后的1990年的Excel表为例:
数据获取可参考:【数据分享】1942-2021全国400多个气象站气候数据 - 知乎
数据介绍可参考:ncdc气象数据格式介绍
关于数据格式转化的代码,推荐参考文章:NCDC气象数据的提取与处理(二):python批量转换isd-lite数据为xlsx 写的非常详细,我也是参考这位大佬的文章注意:在进行处理前,请保证按照这篇文章将格式进行转换,得到待处理的Excel数据再进行下面的步骤
(1)导入包
import os
import pandas as pd
import numpy as np
from tqdm import tqdm # 可视化进度,非必须
pandas、numpy、tqdm需要使用pip安装
(2)设置路径
folderPath = r"E:\b\exp\data\preData\NCDCxlsx"
outputPath = r"E:\b\exp\data\preData\TEST"
folerPath为待处理数据保存的路径,outputPath为最终保存处理好的路径
(3)遍历年份文件及里面的站点文件
nullStationDir = {} # 字典保存站点数据不足的站点文件
folders = os.listdir(folderPath) # 获取上级文件路径下的所有年份文件夹
# 以年为单位遍历
for folder in folders:
fileList = [] # 当年所有站点文件的绝对路径列表
nullStationList = [] # 当年数据不足的站点文件列表
if os.path.splitext(folder)[-1] == "": # 通过后缀判断文件是否为文件夹
# 获取年份文件夹下所有站点文件的绝对路径,并保存到列表fileList中
for root, _, files in os.walk(f"{folderPath}\\{folder}"):
for file in files:
if os.path.splitext(file)[-1] == '.xlsx':
fileList.append(os.path.join(root, file))
fileList保存当年文件夹下的所有站点数据的绝对路径
(4)设置处理后数据的列名
# 初始化一个空的数据框用于之后向里面添加站点数据,根据需要修改,输出Excel你所需的列名,名称可更改
year = pd.DataFrame(columns=['station', 'Date', '平均温度(°C)', '平均露点温度(°C)', '平均气压(hPa)',
'云量', '平均风向(°)', '平均风速(m/s)', '累计降雨(mm)'])
根据自己需要计算的数据设置列名,一般是只需平均温度和累计降雨,我这里每列都处理了,如果只需要平均温度和累计降水,则只需设为[‘station’, ‘Date’, ‘平均温度(°C)’, ‘累计降雨(mm)’],同时需要对(7)处理过程进行一定修改
(5)通过列表遍历读取站点数据
# 获得站点文件个数
# total = len(fileList)
# 通过站点文件的列表遍历年份文件夹下的所有站点文件
# for idx, station in enumerate(fileList, 1):
for stationPath in tqdm(fileList, desc=f"{folder}年站点"):
stationCode = os.path.split(stationPath)[-1][0:6] # 站点文件绝对路径->站点文件名->站点号
# print("*" * 10, stationCode, f'({idx}/{total})', "*" * 10)
# 读取站点文件,根据需要选取列,这里的列名是输入的Excel里面列名,名称不可改
data = pd.read_excel(stationPath, usecols=['Date', '温度', '1小时雨量', '6小时雨量', '露点温度',
'气压', '风向', '风速', '云量'])
data = data.set_index('Date') # 设置日期为索引便于重采样
这里usecols参数是待处理站点数据的列名,所以不能改,不过个数根据year设置的列来选取,比如year只需要平均温度和累计降水,则只需选取[‘Date’, ‘温度’, ‘1小时雨量’, ‘6小时雨量’]
另外这里注释掉的是不用tqdm包时手动打印处理进度
(6)检查站点记录数据缺失情况
# 按月检查站点文件
test = data.resample('m').mean() # 这里以月重采样,如果需要更精细的,如站点每天都要求有数据,m改成d
if test.shape[0] != 12:
nullStationList.append(stationPath)
continue
一些站点数据会有较多缺失,比如有的站点缺失几个月的记录
这里我是按月重采样,来检查是否每月都能保证有记录数据,处理的较粗糙,也可以选取其他重采样频率,但更精细的频率会过滤掉更多站点数据
这里数据不足的站点的绝对路径会被保存到列表中
(7)按年重采样处理
# 将数据按日期重采样到年尺度并计算年均值(温度、露点温度、气压、云量),同时删除不需要列,最后将日期转换为年格式
meanData = data.resample('Y').mean()
meanData.drop(columns=["1小时雨量", "6小时雨量", "风向", "风速"], axis=1, inplace=True)
meanData.index = meanData.index.strftime("%Y")
将待处理数据的温度、露点温度、气压、云量按年取平均。注意这里云量应该不是取平均,因为原始NCDC数据云量的值是离散的代码表示(具体参考ncdc气象数据格式介绍),具体我也不清楚怎么处理。
# 将数据按日期重采样到年尺度并计算年累加值(1小时雨量、6小时雨量),同时删除不需要的温度,最后将日期转换为年格式
sumData = data.resample('Y').sum()
sumData.drop(columns=["温度", "露点温度", "气压", "风向", "风速", "云量",], axis=1, inplace=True)
sumData.index = sumData.index.strftime("%Y")
同时累加1小时和6小时,一个站点只会有6小时或1小时中的一种记录方法,所以这里不会有影响
# 去除无风天后,将数据按日期重采样到年尺度并计算年均值(风向、风速),同时删除不需要列,最后将日期转换为年格式
windData = data[~data["风向"].isin([np.nan,0])]
windMeanData = windData.resample('Y').mean()
windMeanData.drop(columns=["1小时雨量", "6小时雨量", "温度", "露点温度", "气压", "云量"], axis=1, inplace=True)
windMeanData.index = windMeanData.index.strftime("%Y")
关于风速和风向是如何进行年重采样的,这方面我没找到相关计算资料,所以按照自己理解,去除无风天后再按年取平均来计算的
(8)处理后按列合并
# 将均值表和累加表横向关联,合并为新的表,并将1小时和6小时降雨量两列合并,同时添加新的站点列
df = pd.merge(meanData.reset_index(), windMeanData.reset_index())
df = pd.merge(df, sumData.reset_index())
df['降雨'] = df['1小时雨量'] + df['6小时雨量']
df.drop(columns=["1小时雨量", "6小时雨量"], axis=1, inplace=True)
df.insert(0, 'station', int(stationCode)) # 新建一列用于存储站点信息,注意转换数据类型 str->int
(9)处理后按行合并
# 重命名数据框列名,要与year一致
df = df.rename(columns={"温度": "平均温度(°C)", "露点温度": "平均露点温度(°C)", "气压": "平均气压(hPa)",
"风向": "平均风向(°)", "风速": "平均风速(m/s)", "降雨": "累计降雨(mm)"})
# 在每轮循环中将站点数据纵向关联合并到year数据框
year = pd.concat([year, df])
这里重命名必须要与上面设置的year列名一致
(10)保存数据
# 将合并完的年数据重置索引,并将日期转换为整型
df = df.reset_index(drop=True)
df['Date'] = df['Date'].astype(int)
# 保存当年不足数据
nullStationDir[folder] = nullStationList
savePath = f'{outputPath}\\{folder}.xlsx' # 保存路径path可自定义更改
with pd.ExcelWriter(savePath, mode='w') as writer:
year.to_excel(writer, encoding='utf-8', index=False)
这里记录不足数据是通过字典保存的,keys为年份,values为对应年份数据不足站点的路径的列表
给出数据处理部分的完整代码,注释比较详细,有问题和建议欢迎评论~
更新代码:
import os
import pandas as pd
import numpy as np
from tqdm import tqdm
folderPath = r"E:\b\exp\data\preData\NCDCxlsx"
outputPath = r"E:\b\exp\data\preData\TEST"
def Preprocess(folderPath, outputPath):
nullStationDir = {} # 字典保存站点数据不足的站点文件
folders = os.listdir(folderPath) # 获取上级文件路径下的所有年份文件夹
# 以年为单位遍历
for folder in folders:
fileList = [] # 当年所有站点文件的绝对路径列表
nullStationList = [] # 当年数据不足的站点文件列表
if os.path.splitext(folder)[-1] == "": # 通过后缀判断文件是否为文件夹
# 获取年份文件夹下所有站点文件的绝对路径,并保存到列表fileList中
for root, _, files in os.walk(f"{folderPath}\\{folder}"):
for file in files:
if os.path.splitext(file)[-1] == '.xlsx':
fileList.append(os.path.join(root, file))
# 初始化一个空的数据框用于之后向里面添加站点数据,根据需要修改,输出Excel你所需的列名,名称可更改
year = pd.DataFrame(columns=['station', 'Date', '平均温度(°C)', '平均露点温度(°C)', '平均气压(hPa)', '云量', '平均风向(°)', '平均风速(m/s)', '累计降雨(mm)'])
# 获得站点文件个数
# total = len(fileList)
# 通过站点文件的列表遍历年份文件夹下的所有站点文件
# for idx, station in enumerate(fileList, 1):
for stationPath in tqdm(fileList, desc=f"{folder}年站点"):
stationCode = os.path.split(stationPath)[-1][0:6] # 站点文件绝对路径->站点文件名->站点号
# print("*" * 10, stationCode, f'({idx}/{total})', "*" * 10)
# 读取站点文件,根据需要选取列,这里的列名是输入的Excel里面列名,名称不可改
data = pd.read_excel(stationPath, usecols=['Date', '温度', '1小时雨量', '6小时雨量', '露点温度', '气压', '风向', '风速', '云量'])
data = data.set_index('Date') # 设置日期为索引便于重采样
# 按月检查站点文件
test = data.resample('m').mean() # 这里以月重采样,如果需要更精细的,如站点每天都要求有数据,m改成d
if test.shape[0] != 12:
nullStationList.append(stationPath)
continue
# 将数据按日期重采样到年尺度并计算年均值(温度、露点温度、气压、云量),同时删除不需要列,最后将日期转换为年格式
meanData = data[~data["风向"].isin([np.nan,0])]
meanData = data.resample('Y').mean()
meanData.drop(columns=["1小时雨量", "6小时雨量", "风向", "风速"], axis=1, inplace=True)
meanData.index = meanData.index.strftime("%Y")
# 将数据按日期重采样到年尺度并计算年累加值(1小时雨量、6小时雨量),同时删除不需要的温度,最后将日期转换为年格式
sumData = data.resample('Y').sum()
sumData.drop(columns=["温度", "露点温度", "气压", "风向", "风速", "云量",], axis=1, inplace=True)
sumData.index = sumData.index.strftime("%Y")
# 去除无风天后,将数据按日期重采样到年尺度并计算年均值(风向、风速),同时删除不需要列,最后将日期转换为年格式
windData = data[~data["风向"].isin([np.nan,0])]
windMeanData = windData.resample('Y').mean()
windMeanData.drop(columns=["1小时雨量", "6小时雨量", "温度", "露点温度", "气压", "云量"], axis=1, inplace=True)
windMeanData.index = windMeanData.index.strftime("%Y")
# 将均值表和累加表横向关联,合并为新的表,并将1小时和6小时降雨量两列合并,同时添加新的站点列
df = pd.merge(meanData.reset_index(), windMeanData.reset_index())
df = pd.merge(df, sumData.reset_index())
df['降雨'] = df['1小时雨量'] + df['6小时雨量']
df.drop(columns=["1小时雨量", "6小时雨量"], axis=1, inplace=True)
df.insert(0, 'station', int(stationCode)) # 新建一列用于存储站点信息,注意转换数据类型 str->int
# 重命名数据框列名,要与year一致
df = df.rename(columns={"温度": "平均温度(°C)", "露点温度": "平均露点温度(°C)", "气压": "平均气压(hPa)",
"风向": "平均风向(°)", "风速": "平均风速(m/s)", "降雨": "累计降雨(mm)"})
# 在每轮循环中将站点数据纵向关联合并到year数据框
year = pd.concat([year, df])
# 将合并完的年数据重置索引,并将日期转换为整型
df = df.reset_index(drop=True)
df['Date'] = df['Date'].astype(int)
# 保存当年不足数据
nullStationDir[folder] = nullStationList
savePath = f'{outputPath}\\{folder}.xlsx' # 保存路径path可自定义更改
with pd.ExcelWriter(savePath, mode='w') as writer:
year.to_excel(writer, encoding='utf-8', index=False)
return nullStationDir
nullData = Preprocess(folderPath, outputPath)
旧代码(不需要就不用点开了):
import os
import pandas as pd
import numpy as np
def Preprocess(path, save):
"""
TODO: 函数用于将处理好的NCDC数据文件(转换为Excel格式的NCDC数据)进行预处理。预处理包括:计算温度均值、累计降雨量和时间重采样。
Args:
path: 包含年份文件夹的上级文件夹路径
save: 需要保存的文件夹路径
Returns: 保存好的Excel文件
"""
folders = os.listdir(path) # 获取上级文件路径下的所有年份文件夹
print(folders)
for folder in folders: # 以年为单位进行遍历处理
fileList = []
if os.path.splitext(folder)[-1] == "": # 通过后缀判断文件是否为文件夹
# 获取年份文件夹下所有站点文件的绝对路径,并保存到列表fileList中
for root, dirs, files in os.walk(f"{path}\\{folder}"):
for file in files:
if os.path.splitext(file)[-1] == '.xlsx':
fileList.append(os.path.join(root, file))
# 初始化一个空的数据框用于之后向里面添加站点数据
year = pd.DataFrame(columns=['station', 'Date', '温度', '降雨'])
# 获得站点文件个数
total = len(fileList)
# 通过站点文件的列表遍历年份文件夹下的所有站点文件
for idx, station in enumerate(fileList, 1):
stationCode = os.path.split(station)[-1][0:6] # 获取站点编号,站点文件如:450010-99999-2000.xlsx
print("*" * 10, stationCode, f'({idx}/{total})', "*" * 10)
data = pd.read_excel(station, usecols=['Date', '温度', "1小时雨量", "6小时雨量"]) # 筛选所需要的列,可自行修改
data['Date'] = pd.to_datetime(data['Date'])
data = data.set_index('Date') # 设置日期为索引便于重采样
test = data.resample('m').mean()
if test.shape[0] != 12:
print(f'{folder}年{stationCode}站点数据不足!')
continue
# 将数据按日期重采样到年尺度并计算年均值,同时删除不需要的降雨列,最后将日期转换为年格式
meanData = data.resample('Y').mean()
meanData.drop(columns=["1小时雨量", "6小时雨量"], axis=1, inplace=True)
meanData.index = meanData.index.strftime("%Y")
# 将数据按日期重采样到年尺度并计算年累加值,同时删除不需要的温度,最后将日期转换为年格式
sumData = data.resample('Y').sum()
sumData.drop(columns='温度', axis=1, inplace=True)
sumData.index = sumData.index.strftime("%Y")
# 将均值表和累加表横向关联,合并为新的表,并将1小时和6小时降雨量两列合并,同时添加新的站点列
df = pd.merge(meanData.reset_index(), sumData.reset_index())
df['降雨'] = df['1小时雨量'] + df['6小时雨量']
df.drop(columns=["1小时雨量", "6小时雨量"], axis=1, inplace=True)
df.insert(0, 'station', int(stationCode)) # 新建一列用于存储站点信息,注意转换数据类型 str->int
# 在每轮循环中将站点数据纵向关联合并到year数据框
year = pd.concat([year, df])
print(f'{folder}年{stationCode}站点数据添加成功!')
# 将合并完的年数据重置索引,并将日期转换为整型
year = year.reset_index(drop=True)
year['Date'] = year['Date'].astype(int)
'''
数据框格式如下:
--------------------------------------------
station Date 温度 降雨
0 450070 2000 24.029553 790.6
1 450110 2000 22.805576 0.0
2 466860 2000 22.452576 598.3
.. ... ... ... ...
409 599850 2000 27.177576 1552.1
410 599950 2000 27.919750 2180.2
411 599970 2000 27.797267 1021.8
--------------------------------------------
温度:℃ 降雨:mm
'''
# 保存数据
savePath = rf'{save}\\{folder}.xlsx' # 保存路径path可自定义更改
with pd.ExcelWriter(savePath, mode='w') as writer:
year.to_excel(writer, encoding='utf-8', index=False)
print(f"保存{folder}年数据成功")
# 执行数据处理函数
Preprocess(r'E:\b\exp\data\preData\NCDCxlsx', r'E:\b\exp\data\preData\preNCDC')
如果处理csv数据可尝试以下修改:
①要处理的是csv数据,修改25行和37行代码,将后缀判断改为".csv",读取方式改为"read_csv"
# if os.path.splitext(file)[-1] == '.xlsx':
if os.path.splitext(file)[-1] == '.csv':
# data = pd.read_excel(station, usecols=['Date', '温度', "1小时雨量", "6小时雨量"])
data = pd.read_csv(station, usecols=['Date', '温度', "1小时雨量", "6小时雨量"])
但是要注意内容、格式、命名等应该与之前excel一样,内容参考NCDC,列名如下:
如果只是列名不同,也是再37行修改为对应列名就行。如果内容不一样,代码就无法使用了,中间部分都需要修改,不过处理思路比较简单(数据读取为DataFrame格式进行处理,数据必须有日期列以用于时间重采样,再将DataFrame转为需要的格式)。
②处理后保存为csv
如果保存格式需要csv,对84-86行修改如下,这里我自己试的时候用"utf-8"编码会出现乱码。
# with pd.ExcelWriter(savePath, mode='w') as writer:
# year.to_excel(writer, encoding='utf-8', index=False)
# print(f"保存{folder}年数据成功")
year.to_csv(savePath, encoding='utf_8_sig', index=False)
print(f"保存{folder}年数据成功")
在重采样处理后的基础上加上关于站点的信息,主要添加了经纬度、高程、省份及省份码,有部分站点(大概20个左右)在岛屿上不好获得省份,下面是大致效果:
最后分享下我处理的1990-2020年的数据
链接:https://pan.baidu.com/s/1PJH2CVZO_E5QLea5w1RteA?pwd=gzp3
提取码:gzp3