当前位置:首页>python>Python | 压缩近3倍netcdf文件大小

Python | 压缩近3倍netcdf文件大小

  • 2026-09-20 17:56:52
Python | 压缩近3倍netcdf文件大小

起因

我为了买不起硬盘的事,硬生生把netcdf文件压缩了2.70倍


正文

各位,容我给大家讲一个我关于 "消费降级" 的故事。

9个月前我激情满怀地花了6000块买了两块26TB硬盘,当时觉得自己是个大数据玩家。

结果前两天逛淘宝想再买一块,发现硬盘涨价了,我居然还能赚点钱卖出去,这买卖亏翻了。而且我看了眼硬盘的价格,直接跳起来了,这群人是活着的奸商吧?

既然买不起新硬盘了,只能另想办法。于是我开始琢磨怎么"压缩"数据,就像精明的家庭主妇会把生活开支压缩一样。

于是用这个代码压缩了我的netcdf文件:

import numpy as np
import xarray as xr
import os
import time

OUTPUT_DIR = "."
DUMMY_FILE = os.path.join(OUTPUT_DIR, "dummy_input.nc")
FILE_BASELINE = os.path.join(OUTPUT_DIR, "result_no_compression.nc")
FILE_COMPRESSED = os.path.join(OUTPUT_DIR, "result_scale_zlib.nc")

SHAPE = (12, 192, 288)
VAR_NAME = "temperature"

defgenerate_dummy_data():
    t_steps, lats, lons = SHAPE

    time_coord = np.arange(t_steps)
    lat_coord = np.linspace(-90, 90, lats)
    lon_coord = np.linspace(0, 360, lons, endpoint=False)

    t, y, x = np.meshgrid(time_coord, lat_coord, lon_coord, indexing='ij')
    data = (
280 * np.ones_like(t) +
20 * np.sin(np.radians(y)) +
5 * np.cos(np.radians(x) * 2 + t * 0.1) +
        np.random.normal(0, 0.1, SHAPE)
    ).astype(np.float32)

    ds = xr.Dataset(
        {VAR_NAME: (['time', 'lat', 'lon'], data)},
        coords={
'time': time_coord,
'lat': lat_coord,
'lon': lon_coord
        }
    )

    ds.to_netcdf(DUMMY_FILE, encoding={VAR_NAME: {'dtype': np.float32}})
return ds

defget_manual_encoding(ds):
    encoding_dict = {}
    var_name = VAR_NAME
    da = ds[var_name]

    data_min = float(da.min(skipna=True))
    data_max = float(da.max(skipna=True))

    dtype = np.int16
    int_min = np.iinfo(dtype).min
    int_max = np.iinfo(dtype).max

if data_max == data_min:
        scale_factor = 1.0
        add_offset = data_min
else:
        scale_factor = (data_max - data_min) / (int_max - int_min)
        add_offset = data_min - int_min * scale_factor

    encoding_dict[var_name] = {
'dtype': dtype,
'scale_factor': scale_factor,
'add_offset': add_offset,
'_FillValue': np.iinfo(dtype).min,
'zlib': True,
'complevel': 4,
'shuffle': True
    }

return encoding_dict

defrun_test():
    ds_orig = generate_dummy_data()

    start_a = time.time()
    ds_orig.to_netcdf(FILE_BASELINE, encoding={VAR_NAME: {'dtype': np.float32}})
    time_a = time.time() - start_a
    size_a = os.path.getsize(FILE_BASELINE) / (1024 * 1024)

    encoding_manual = get_manual_encoding(ds_orig)

    start_b = time.time()
    ds_orig.to_netcdf(FILE_COMPRESSED, encoding=encoding_manual)
    time_b = time.time() - start_b
    size_b = os.path.getsize(FILE_COMPRESSED) / (1024 * 1024)

    ds_comp_loaded = xr.open_dataset(FILE_COMPRESSED)

    orig_vals = ds_orig[VAR_NAME].values
    comp_vals = ds_comp_loaded[VAR_NAME].values

    mask = ~(np.isnan(orig_vals) | np.isnan(comp_vals))
    diff = orig_vals[mask] - comp_vals[mask]
    rmse = np.sqrt(np.mean(diff**2))
    max_err = np.max(np.abs(diff))

    ratio = size_a / size_b
    saved_pct = ((size_a - size_b) / size_a) * 100

    results = {
"size_original_mb": size_a,
"size_compressed_mb": size_b,
"saved_percent": saved_pct,
"compression_ratio": ratio,
"time_diff_s": time_b - time_a,
"rmse": rmse,
"max_error": max_err
    }

return results

if __name__ == "__main__":
    results = run_test()
    print(f"Original Size: {results['size_original_mb']:.2f} MB")
    print(f"Compressed Size: {results['size_compressed_mb']:.2f} MB")
    print(f"Saved: {results['saved_percent']:.1f}%")
    print(f"Ratio: {results['compression_ratio']:.2f}x")
    print(f"RMSE: {results['rmse']:.6e}")
    print(f"Max Error: {results['max_error']:.6f}")

测试结果?我直接笑了:

  • 原始Float32文件:2.54 MB
  • 压缩后Int16文件:0.94 MB
  • 节省空间:63%
  • 压缩倍数:2.70x

核心秘诀就这几行:

encoding={
'dtype': np.int16,           # Float32 -> Int16,直接瘦身
'scale_factor': 0.000771,    # 精准缩放,误差微乎其微
'add_offset': 279.9932,      # 偏移调整,保留有效数据
'zlib': True,                # 再配上zlib压缩
'complevel': 1,              # 压到4档就够了
'shuffle': True# Shuffle洗牌增强压缩效果
}

数据精度损失?我测了测,RMSE才2.2e-4,最大误差0.0004,这点误差比我的实验误差还小。对于大气科学来说,这简直是完美。

Climate Data Store上提供的legacy的ERA5数据之前也是这样压缩分发给用户的,所以不用担心数据精度损失。

所以?

与其花钱买新硬盘,不如学点代码节省空间。这些硬盘厂商就算涨价3倍我也不怕了,因为我的数据现在足够"苗条",硬盘用到地老天荒都没问题。

附注

这代码对所有处理大型格点数据的朋友都有帮助:

  • 气象/气候数据
  • 海洋模式输出
  • 其他高维数据

量化压缩(Quantization)+ zlib已经是金牌搭档了。你的SSD、机械硬盘的费用,都能省一笔。

最新文章

随机文章