三维xarray Dataset转换为Pandas DataFrame时保留原维度结构的方法及问题解决
我完全懂你碰到的这个麻烦——把带(time, lat, lon)维度的xarray数据集转成DataFrame时,默认操作直接把所有维度揉成多层索引,数据全挤在一列里,哪怕指定dim_order也没改观,确实挺闹心的。
先复盘下你的场景
你先是导入并筛选了数据集:
from xgrads import open_CtlDataset ds_Snow = open_CtlDataset(path + 'file') ds_Snow = ds_Snow.sel(lat = list(set(zones['lat'])), lon = list(set(zones['lon'])), time = period, method = 'nearest')
这个数据集的维度是(lat: 12, lon: 12, time: 30),但执行ds_Snow.to_dataframe()后,得到的却是一个以lat、lon、time为多层索引的单列DataFrame,4320行数据全堆在一列里。
为啥会这样?
xarray的to_dataframe()默认逻辑就是把所有坐标维度转成DataFrame的多层索引(MultiIndex),数据变量则作为单列(多变量的话是多列)。你指定的dim_order只是调整多层索引的顺序,并不会把这些维度拆成单独的列——这就是为啥设置后结果没变化的原因。
给你几个解决方案
方案1:你自己摸索出来的手动构建法
你通过提取每个坐标和数据变量的Series来拼DataFrame,这种方式直接可控,特别适合你想把维度都做成单独列的需求:
ds_Snow = ds_Snow.sel(lat = list(set(station_list['lat_utm'])),lon = list(set(station_list['lon_utm'])), time = Ind_Run_ERA5_Land, method = 'nearest') time = pd.Series(ds_Snow.coords["time"].values) lon = pd.Series(ds_Snow.coords["lon"].values) lat = pd.Series(ds_Snow.coords["lat"].values) spre = pd.Series(ds_Snow['spre'].values[:,0,0]) # 这里取[:,0,0]是因为你的lat/lon维度值全重复 frame = { 'spre': spre, 'time': time, 'lon' : lon, 'lat' : lat} df_Snow = pd.DataFrame(frame)
方案2:用xarray+Pandas内置方法简化操作
其实不用手动拼Series,转换后直接重置索引就能把多层索引转成列:
# 先转成带多层索引的DataFrame,再把索引维度转成普通列 df_Snow = ds_Snow.to_dataframe().reset_index()
这样得到的DataFrame会有time、lat、lon、spre四个列,每行对应一组(time, lat, lon)的spre值,完美匹配你想要的结构。
方案3:如果需要宽表结构(lat/lon作为列,time做索引)
要是你想要更贴近原数组的二维结构(比如把不同lat/lon组合作为列,time作为索引),可以用unstack():
# 把lat和lon从索引展开成列 df_Snow_wide = ds_Snow.to_dataframe().unstack(['lat', 'lon'])
这样得到的DataFrame索引是时间,每一列对应一个(lat, lon)位置的spre时间序列。
小补充
从你的数据集信息能看到,lat和lon维度的所有值都是重复的(比如lat全是3.414e+06),这应该是因为你用list(set(zones['lat']))筛选后,维度长度保留了12,但实际值只有一个。这种情况下你取[:,0,0]完全合理,因为其他位置的值和第一个一模一样。要是后续你的lat/lon有不同值,记得把[:,0,0]改成ds_Snow['spre'].values.flatten(),这样能获取所有(time, lat, lon)组合的spre值,再对应到每个维度的组合上。
内容的提问来源于stack exchange,提问作者C Elise

