Python嵌套for循环如何沿新维度存储NetCDF读取的多维数组
问题背景
- 本人为Python初学者,此前有Matlab使用经验,目前正迁移到Python开展数据处理工作
- 需求为通过for循环批量读取多个
.nc(NetCDF)格式文件,沿新增的记录维度存储读取到的变量数据 - 单次循环读取得到的变量
j、j1维度均为4×30×30,需要遍历exp列表的3个元素,将获取到的j变量数据存入目标变量appn的第0维,最终得到维度为3×4×30×30的appn数组 - 该操作在Matlab中实现逻辑简单,但在Python环境下暂未找到正确实现方式,自行编写的代码存在语法错误、维度不匹配问题
原错误代码
import os, sys import netCDF4 from mpl_toolkits.basemap import Basemap import numpy as np import matplotlib.pyplot as plt from cdo import * import xarray as xr cdo=Cdo() indices = ["T"] models = ["MJ", "MK", "ML"] seasons =["JJA", "DJF"] period =["base", "proj"] exp = ["ssp1", "ssp2", "ssp3"] odir = "/outs_pytry/season/" top ="/outs_pytry/monmean_1/" os.makedirs(odir, exist_ok=True) #appn=[] appnx=[] #pr_arr = np.zeros([models,nlat,nlon], dtype='f4') #pr_arr = np.zeros([], dtype='f4') j=[] for m in models: folder = "%s"%(top) if m in ["ML"]: run = "r1i1p1f2" else: run = "r1i1p1f1" for i in indices: for e in exp: origfi1 = '%s%s_%s_%s_%s_base.nc'%(folder,i, m, e, run) origfi2 = '%s%s_%s_%s_%s_proj.nc'%(folder,i, m, e, run) k=cdo.timselmean(3,11,9, input="%s"%origfi1, output="%s%s_%s_%s_%s_DJF_proj.nc"%(odir,i,m,e,run), returnCdf=True) k1=cdo.timselmean(3,5,9, input="%s"%origfi1, output="%s%s_%s_%s_%s_JJA_proj.nc"%(odir,i,m,e,run), returnCdf=True) j=k.variables["T"][:] j1=k1.variables["T"][:] lat=k.variables["lat"] lon=k.variables["lon"] #appn=np.zeros([3,4,lon,lat], dtype='f4') datain = np.array(j) #Confused with how to store data in appn , so that it has a fourth dimension of size 'e'? appn(e,:,:,:) =datain appn.append(datain)
错误原因
- 语法错误:Python中数组/列表索引使用方括号
[],和Matlab用圆括号()的规则不同,原代码里appn(e,:,:,:) = datain本身不符合Python语法 - 逻辑错误:原代码把两种数组存储逻辑混写,既尝试类Matlab的预分配数组按索引赋值,又尝试列表追加,两种逻辑二选一即可实现需求
- 性能隐患:如果直接在循环里用
np.append()拼接数组,每次拼接都会全量拷贝内存,数据量大时运行速度会极慢
实现方案
方案1:预分配numpy数组(和Matlab使用习惯一致)
适合提前明确最终数组维度的场景,内存连续运行效率高,和Matlab预分配内存的操作逻辑完全对齐:
- 在进入
exp循环前,初始化维度匹配的零值数组,第一维长度为exp列表的长度3,后三维和单次读取的j维度保持一致为4×30×30 - 循环中增加索引计数器,每读取一个
datain就写入数组对应索引的切片位置
对应修正代码片段:
# ------------ 循环外初始化(放在exp循环外层即可)------------ appn = np.zeros([len(exp), 4, 30, 30], dtype='f4') exp_idx = 0 # 标记当前写入的exp维度位置 # ------------ exp循环内逻辑 ------------ for e in exp: # 原有文件读取、cdo计算逻辑保留不变 origfi1 = '%s%s_%s_%s_%s_base.nc'%(folder,i, m, e, run) origfi2 = '%s%s_%s_%s_%s_proj.nc'%(folder,i, m, e, run) k=cdo.timselmean(3,11,9, input="%s"%origfi1, output="%s%s_%s_%s_%s_DJF_proj.nc"%(odir,i,m,e,run), returnCdf=True) k1=cdo.timselmean(3,5,9, input="%s"%origfi1, output="%s%s_%s_%s_%s_JJA_proj.nc"%(odir,i,m,e,run), returnCdf=True) j=k.variables["T"][:] j1=k1.variables["T"][:] lat=k.variables["lat"] lon=k.variables["lon"] datain = np.array(j) # 注意索引使用方括号 appn[exp_idx, :, :, :] = datain exp_idx += 1 # 索引后移,准备写入下一个exp的数据
循环结束后appn就是维度为3×4×30×30的目标数组。如果后续需要存储多模型、多指数的结果,只需要对应调整预分配数组的第一维长度即可。
方案2:列表暂存后一次性拼接
适合不确定最终数组长度的场景,不需要提前计算维度:
- 循环外初始化一个空列表
- 每次循环读取到
datain后,直接追加到列表中 - 所有循环结束后,用
np.stack()沿第0维拼接列表内的数组,直接得到目标维度的结果
对应修正代码片段:
# ------------ 循环外初始化 ------------ appn_list = [] # ------------ exp循环内逻辑 ------------ for e in exp: # 原有文件读取、cdo计算逻辑保留不变 # ... 省略重复的文件读取代码 ... datain = np.array(j) appn_list.append(datain) # ------------ 循环结束后拼接数组 ------------ appn = np.stack(appn_list, axis=0)
拼接完成后appn的维度自动为3×4×30×30,和需求一致。
注意:不要在循环中反复调用
np.append()拼接numpy数组,效率极低。
内容的提问来源于stack exchange,提问作者xxsou
相关产品推荐
相关产品推荐

