python实现美国国家气候数据中心NCDC预处理,按年重采样为年度数据,并保存为Excel格式

        最近学习了处理气象数据NCDC 的过程,在这里记录并分享一下,由于刚开始学相关知识,文中有疏漏和不足,还请多见谅。

        4.3更 摸了好久,决定还是更新一下,主要是稍微改了一点点代码,把其他列的数据也处理了,顺便添了一些站点信息,最后附上我处理好的数据。


目录

前言

一、处理步骤

1 获取待处理数据

(1)NCDC数据获取

(2)NCDC数据处理为xlsx格式

2 处理数据过程

二、完整代码

三、代码说明

1.处理CSV格式数据

2.添加经纬度等信息


前言

本文实现内容:对于格式转换后的NCDN数据(按年份保存的站点xlsx格数数据),将每年的站点数据按年重采样的各要素数据(气温、降水、气压等),并添加站点列,最按年后保存到一张Excel表中。
下面用图来表示
这里是待处理的数据,包含1990-2020共31个文件夹:

python实现美国国家气候数据中心NCDC预处理,按年重采样为年度数据,并保存为Excel格式_第1张图片

每个文件夹下有几百个当年站点数据的Excel表文件,以1990年的文件为例:

python实现美国国家气候数据中心NCDC预处理,按年重采样为年度数据,并保存为Excel格式_第2张图片

每个站点Excel表中又有几千行记录数据:

python实现美国国家气候数据中心NCDC预处理,按年重采样为年度数据,并保存为Excel格式_第3张图片

接下来是处理后的数据,这些站点数据的几千行记录数据被按年重采样到一行,然后再将每年的几百行站点数据保存到一张表中,最终保存为31个Excel表,以处理后的1990年的Excel表为例:

python实现美国国家气候数据中心NCDC预处理,按年重采样为年度数据,并保存为Excel格式_第4张图片


一、处理步骤

1 获取待处理数据

(1)NCDC数据获取

​数据获取可参考:【数据分享】1942-2021全国400多个气象站气候数据 - 知乎
数据介绍可参考:ncdc气象数据格式介绍​

(2)NCDC数据处理为xlsx格式

关于数据格式转化的代码,推荐参考文章:NCDC气象数据的提取与处理(二):python批量转换isd-lite数据为xlsx 写的非常详细,我也是参考这位大佬的文章
注意:在进行处理前,请保证按照这篇文章将格式进行转换,得到待处理的Excel数据再进行下面的步骤

2 处理数据过程

(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

一些站点数据会有较多缺失,比如有的站点缺失几个月的记录

python实现美国国家气候数据中心NCDC预处理,按年重采样为年度数据,并保存为Excel格式_第5张图片


这里我是按月重采样,来检查是否每月都能保证有记录数据,处理的较粗糙,也可以选取其他重采样频率,但更精细的频率会过滤掉更多站点数据
这里数据不足的站点的绝对路径会被保存到列表中

(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')

三、代码说明

1.处理CSV格式数据

如果处理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,列名如下:

python实现美国国家气候数据中心NCDC预处理,按年重采样为年度数据,并保存为Excel格式_第6张图片

        如果只是列名不同,也是再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}年数据成功")

2.添加经纬度等信息

在重采样处理后的基础上加上关于站点的信息,主要添加了经纬度、高程、省份及省份码,有部分站点(大概20个左右)在岛屿上不好获得省份,下面是大致效果:

python实现美国国家气候数据中心NCDC预处理,按年重采样为年度数据,并保存为Excel格式_第7张图片

最后分享下我处理的1990-2020年的数据

链接:https://pan.baidu.com/s/1PJH2CVZO_E5QLea5w1RteA?pwd=gzp3 
提取码:gzp3 

你可能感兴趣的:(python)