直接上手xarray处理数据,这套教程能帮你少走三个月弯路
做气象、海洋、遥感或者任何跟格点数据打交道的工作,迟早会撞上xarray这个库。我第一次接触它是在处理CMIP6模式输出的时候,几万个netCDF文件堆在服务器上,用numpy硬啃差点把自己劝退。后来花了两周时间把xarray捋顺,回头再看那些数据,处理效率提升得不是一点半点。这篇教程我把从入门到实战的完整路径整理出来,该避的坑、该记的API、该理解的底层逻辑都在里面,希望能帮你省下我当初踩坑的时间。
xarray本质上是带着标签的numpy数组,它把维度的名字、坐标的值、属性的信息全部揉进数据本身。这句话理解了,xarray就学会了一半。传统numpy数组只有轴的概念,轴0、轴1、轴2,你写代码的时候脑子里得记着每个轴代表什么,时间长了或者在多个文件之间来回切换的时候,很容易搞混。xarray直接给每个维度起名字,给每个坐标赋值,让数据自己说明自己是谁、在哪、什么时候。
这套设计思路跟pandas的index有异曲同工之妙,但pandas只能处理二维表格,xarray把这种思想推广到了任意维度的数组上。做气候数据的知道,一个典型的降水数据通常是(时间, 纬度, 经度)三维,再叠加多个变量就成了Dataset;做遥感的会碰到(波段, 行, 列),做点云的可能是(帧, 点索引)这种变长维度。这些结构用numpy表达很痛苦,用xarray表达却很自然。
这篇教程适合所有需要跟多维带标签数组打交道的人。不管你是刚开始接触Python数据处理的新手,还是已经用numpy/pandas很久但被维度管理折磨的老手,只要跟着这套路径走一遍,基本能从只会“用xarray读文件”进阶到“用xarray设计和解决实际数据处理流程”。
1. 核心设计思路与数据模型拆解
1.1 为什么是xarray而不是numpy或pandas
我在实际项目中遇到过太多用numpy硬写导致翻车的例子。最典型的就是处理站点观测数据时,不同站点的观测时段还不一样长,用numpy的二维数组存,只能靠掩码或者nan来填补,然后每个操作都要手动跟踪哪些位置是有效的。这就像把一堆带标签的卡片按固定位置塞进抽屉,时间久了标签掉了、顺序乱了,数据就没法用了。
pandas能解决二维问题,但处理不了三维以上的数据。你当然可以把三维数据压平成一个长表格,用MultiIndex来做索引,可一旦要按纬度做加权平均、按季节做气候态、插值到新网格,写出来的代码又绕又慢。
xarray站在了这两者的肩膀上。它做对了三件事:
- 把维度名字变成一等公民,不再用“轴0”“轴1”这种无意义编号;
- 用坐标对象记录每个位置的实际含义(时间点、经纬度、高度层);
- 提供了一套按名字对齐和运算的规则,两个数据集的维度顺序不一致也能直接做运算。
实际使用中最直观的感受是:你再也不用担心两个数组的维度顺序对不上,也不用每次写完运算都花半小时确认结果到底对不对。xarray会自动按维度名对齐,结果里的维度顺序由坐标决定,而不是靠传参顺序碰运气。
1.2 从一次翻车经历理解维度与坐标
去年处理一套高分辨率模式输出时,我遇到过一个自己都觉得离谱的情况。数据是从别人那儿拷来的,变量名叫precip,维度顺序是(经度, 纬度, 时间)。我当时想当然地认为是(时间, 纬度, 经度),直接用arr[:, 0, 0]取第一个格点的时间序列,结果拿到的是一堆按经度排列的乱码。
这种错误在numpy里是常态,可在xarray里几乎不可能发生。你可以直接:
python复制import xarray as xr
ds = xr.open_dataset('precip.nc')
# 直接按维度名取数据,不关心原始顺序
time_series = ds['precip'].sel(lat=30, lon=120)
用维度名和坐标值取数据,而不是用位置编号硬切,这是xarray最重要的思维方式转变。sel()是按坐标值选取,isel()才是按下标位置选取,这两者在日常操作中都要用到,要区分清楚。
1.3 核心数据结构:DataArray和Dataset的分工
xarray里就两个核心类:DataArray和Dataset。可以这样理解:DataArray是单变量数组,比如“2020年1月1日到2020年12月31日全国逐日降水”;Dataset是多个DataArray的集合,比如同一个文件里既有降水又有温度、风速、湿度等一堆变量。
这种设计的妙处在于:各变量可以有不同的维度。比如降水是三维的,但站点的海拔高度只有(站点)一维,它照样能共存于同一个Dataset中。处理数据时不仅变量名可以对应,维度坐标也能统一对齐,用起来非常顺。后面所有实操,都是围绕这两个对象展开的。
python复制# 创建一个简单的DataArray
import numpy as np
da = xr.DataArray(
data=np.random.rand(3, 4, 5),
dims=['time', 'lat', 'lon'],
coords={
'time': ['2024-01-01', '2024-01-02', '2024-01-03'],
'lat': [30, 31, 32, 33],
'lon': [120, 121, 122, 123, 124]
},
name='temperature',
attrs={'units': 'K', 'long_name': 'air_temperature'}
)
print(da)
输出会展示维度、坐标和你传进去的数据,一眼就能看清整个结构。attrs用来存元数据,做数据处理时很多信息(单位、来源、处理步骤)都能记录在这里,保存文件后再读出来也不会丢。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与数据读取核心要点
2.1 安装和依赖问题
xarray的安装很简单,但依赖这一环必须注意。基础安装直接:
bash复制pip install xarray
如果要处理netCDF文件,还需要netCDF4和h5netcdf两个后端之一。建议两个都装,因为有的文件用netCDF4后端读不了,换h5netcdf反而能读出来。处理大型数据集时建议把dask也装上,这样xarray会自动支持延迟计算和并行处理,后面我会专门讲。
bash复制pip install xarray netCDF4 h5netcdf dask[array]
如果用的是conda,可以:
bash复制conda install -c conda-forge xarray netcdf4 h5netcdf dask
conda-forge的版本更新更及时,而且能解决一些底层库的依赖冲突问题。我个人的习惯是优先用conda管理科学计算环境,pip只负责一些conda里没有的纯Python包。
2.2 常见的文件格式怎么读
xarray对NetCDF、GRIB、Zarr、GeoTIFF等格式支持都很好,读取方式各有讲究。这是我最常用的几类:
| 文件格式 | 典型库后端 | 读取方式 | 场景 |
|---|---|---|---|
| netCDF | netCDF4 / h5netcdf | xr.open_dataset() |
气候/海洋模式输出 |
| GRIB | cfgrib | xr.open_dataset(engine='cfgrib') |
气象预报产品 |
| Zarr | zarr | xr.open_dataset(engine='zarr') |
云存储/分布式处理 |
| GeoTIFF | rioxarray | xr.open_rasterio() |
遥感影像 |
| 内存numpy | - | xr.DataArray() / xr.Dataset() |
中间处理结果 |
先说netCDF,这是气候和地球科学领域最常见的数据格式。它是自描述的,文件里包含了变量、维度、坐标和属性的完整信息,非常适合xarray读取。一个典型的读取操作:
python复制import xarray as xr
ds = xr.open_dataset('CMIP6_pr_day.nc')
print(ds)
通常输出会展示维度大小、坐标范围和变量列表。很多新手问,为什么打印出的变量名和同事论文里写的不一样?因为每个数据集的定义不同,pr、precip、rain可能都是降水。这时候跳转ds.info()或者仔细看attrs里的long_name和standard_name字段就清楚了。我强烈建议在拿到数据的第一步,先打印print(ds)完整审视一遍结构,再动手后续操作。
GRIB是气象领域的另一种常见格式,尤其在数值预报产品中。xarray没有内置GRIB读写的API,需要额外安装cfgrib:
bash复制pip install cfgrib
然后:
python复制ds = xr.open_dataset('forecast.grib2', engine='cfgrib')
一个比较坑的地方是,GRIB文件里的参数名和netCDF很不一致,比如t可能是温度,u/v是风分量,但具体代表什么高度、什么层次,需要通过attrs里的GRIB_shortName和typeOfLevel来判断。另外GRIB文件经常一个物理量一个文件,读进来做多变量拼接时会比较繁琐。
GeoTIFF是遥感领域最常见的格式,xarray本身不能直接读取,需要配合rioxarray:
bash复制pip install rioxarray
python复制da = xr.open_rasterio('LC08_L1TP_012033_20200101.tif')
读取之后它是带spatial_ref坐标的DataArray,可以直接继续做裁剪、重投影等操作。这里提醒一下,open_rasterio默认把波段放在band维度,但在实际处理中经常需要把它跟经纬度坐标关联起来。
如果你拿到的是内存中的numpy数组,想转成xarray对象,也很简单:
python复制arr = np.random.rand(365, 180, 360)
da = xr.DataArray(
arr,
dims=['time', 'lat', 'lon'],
coords={
'time': xr.date_range('2020-01-01', periods=365, freq='D'),
'lat': np.linspace(-90, 90, 180),
'lon': np.linspace(0
