爱气象,爱气象家园! 

气象家园

 找回密码
 立即注册

QQ登录

只需一步,快速开始

新浪微博登陆

只需一步, 快速开始

搜索
查看: 194|回复: 9

[经验总结] 如何用 python 对离散数据做二维分箱?

[复制链接]

新浪微博达人勋

发表于 7 天前 | 显示全部楼层 |阅读模式

登录后查看更多精彩内容~

您需要 登录 才可以下载或查看,没有帐号?立即注册 新浪微博登陆

x
本帖最后由 灭火器 于 2025-1-15 22:44 编辑

已知一系列离散点,用经纬度和变量值描述,可以保存为如下的二维表格:

Snipaste_2025-01-14_18-09-50.png

二维分箱(2d binning)指规划好经纬度的正交网格,根据每个格子里落入的离散点计算格子的统计值,最后得到各个变量的格点数据,然后可以保存为 netCDF 文件。将非规则网格的 L2 卫星数据处理成格点数据时,经常会利用二维分箱的方法计算格子里的平均值。大概类似 NCL 官网这张图的效果:

binning_2_lg.png

NCL 里直接有现成的 bin_avg 函数,但 python 里要实现就更麻烦一些。

这里举出我常用的两种方法,就当抛砖引玉,想看看大家都是怎么实现的。简单粗暴的二维循环就忽略了。

方法一:scipy.stats.binned_statistic_2d,注意为了得到形如 (nlat, nlon) 的结果,需要颠倒 x 和 y 参数。
  1. from scipy.stats import binned_statistic_2d


  2. def binning2d(
  3.     x: ArrayLike,
  4.     y: ArrayLike,
  5.     values: ArrayLike | list[ArrayLike],
  6.     xbins: int | ArrayLike,
  7.     ybins: int | ArrayLike,
  8. ) -> tuple[NDArray, NDArray, NDArray]:
  9.     def nanmean(arr: NDArray) -> float:
  10.         arr = arr[~np.isnan(arr)]
  11.         if arr.size == 0:
  12.             return np.nan
  13.         return arr.mean()

  14.     binned, ybins, xbins, _ = binned_statistic_2d(
  15.         y, x, values, bins=[ybins, xbins], statistic=nanmean
  16.     )
  17.     xlabels = get_bin_centers(xbins)
  18.     ylabels = get_bin_centers(ybins)

  19.     return xlabels, ylabels, binned


  20. df = pd.read_csv("test.csv")

  21. lon_bins, lon_labels = make_evenly_bins(-180, 180, 0.1)
  22. lat_bins, lat_labels = make_evenly_bins(-90, 90, 0.1)

  23. varnames = df.columns[2:]
  24. _, _, binned = binning2d(
  25.     x=df["lon"],
  26.     y=df["lat"],
  27.     values=[df[varname] for varname in varnames],
  28.     xbins=lon_bins,
  29.     ybins=lat_bins,
  30. )

  31. ds = xr.Dataset(
  32.     {varname: (["lat", "lon"], data) for varname, data in zip(varnames, binned)},
  33.     coords={"lon": lon_labels, "lat": lat_labels},
  34. )
复制代码

方法二:pd.cut,更加简洁,速度也比 scipy 快很多。
更新:根据墨家大宝的回复修改了 observed 参数,并使用 reindex。
  1. import numpy as np
  2. import pandas as pd
  3. import xarray as xr
  4. from numpy.typing import ArrayLike, NDArray


  5. def get_bin_centers(bins: ArrayLike) -> NDArray:
  6.     bins = np.asarray(bins)
  7.     assert len(bins) >= 2
  8.     return (bins[1:] + bins[:-1]) / 2


  9. def linspace2(x0: float, x1: float, dx: float) -> NDArray:
  10.     assert dx > 0
  11.     nx = int(abs(x1 - x0) / dx) + 1
  12.     x = np.linspace(x0, x1, nx)

  13.     return x


  14. def make_evenly_bins(x0: float, x1: float, dx: float) -> tuple[NDArray, NDArray]:
  15.     bins = linspace2(x0, x1, dx)
  16.     labels = get_bin_centers(bins)

  17.     return bins, labels


  18. lon_bins, lon_labels = make_evenly_bins(-180, 180, 0.1)
  19. lat_bins, lat_labels = make_evenly_bins(-90, 90, 0.1)

  20. df = pd.read_csv("test.csv")

  21. # 实际运算部分
  22. ds = (
  23.     df.assign(
  24.         lon=pd.cut(df["lon"], lon_bins, labels=lon_labels, include_lowest=True),
  25.         lat=pd.cut(df["lat"], lat_bins, labels=lat_labels, include_lowest=True),
  26.     )
  27.     .groupby(["lat", "lon"], observed=True)
  28.     .mean()
  29.     .to_xarray()
  30.     .reindex(lon=lon_labels, lat=lat_labels)
  31. )

  32. ds.to_netcdf("test.nc")
复制代码


另外还有两种我尝试失败的 xarray 方法,看有没有坛友知道原因:

  1. lon_cat = pd.cut(df['lon'], lon_bins, labels=lons, include_lowest=True)
  2. lat_cat = pd.cut(df['lat'], lat_bins, labels=lats, include_lowest=True)
  3. ds = df.to_xarray().assign(lon=('index', lon_cat), lat=('index', lat_cat)).groupby(['lat', 'lon']).mean()

  4. # v2024.07.0 版本以上
  5. from xarray.groupers import BinGrouper
  6. lon_grouper = BinGrouper(lon_bins, labels=lons, include_lowest=True)
  7. lat_grouper = BinGrouper(lat_bins, labels=lats, include_lowest=True)
  8. ds = df.to_xarray().groupby(lon=lon_grouper, lat=lat_grouper).mean()
复制代码


密码修改失败请联系微信:mofangbao

新浪微博达人勋

发表于 6 天前 | 显示全部楼层
试试我的秘方:

df[['lon', 'lat']] = df[['lon', 'lat']] // 0.1 * 0.1 +0.05
da = df.groupby(['lon', 'lat']).mean().to_xarray().reindex(lat=lats, lon=lons)
密码修改失败请联系微信:mofangbao
回复 支持 1 反对 0

使用道具 举报

新浪微博达人勋

发表于 6 天前 | 显示全部楼层
{:5_213:}{:5_213:}
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

发表于 6 天前 来自手机 | 显示全部楼层
groupby即可。
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

发表于 6 天前 来自手机 | 显示全部楼层
本帖最后由 edwardli 于 2025-1-15 15:47 编辑

groupby即可。pandas.dataframe和xarray.dataset都有这个功能。

道理同此:  dailyTP.groupby('latitude').mean(dim="longitude")

【气小Py-007:中期预报降水趋势_2023.06.13-哔哩哔哩】【视频标记点 04:00】 https://b23.tv/p81Hsp8
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

 楼主| 发表于 6 天前 | 显示全部楼层
墨家大宝 发表于 2025-1-15 14:30
试试我的秘方:

df[['lon', 'lat']] = df[['lon', 'lat']] // 0.1 * 0.1 +0.05

学到了,确实是秘方,量化到 0.1 分辨率再加一半的分辨率,相当于得到了分箱的标签,然后做 groupby,虽然有些格子没有出现在结果里,但是最后通过 reindex 把这些格子(行和列)又补回来了,更快也更省内存。
不过秘方有两个假设:一是格子是等间距的,二是格子的左右边缘数值应该是分辨率的整数倍,比如说 70、70.1、70.2,而 70.05 就会产生一些误差。不过估计减去 minlon 再加上 minlon 就能解决。
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

 楼主| 发表于 6 天前 | 显示全部楼层
edwardli 发表于 2025-1-15 15:42
groupby即可。pandas.dataframe和xarray.dataset都有这个功能。

道理同此:  dailyTP.groupby('latitude ...

看了一下视频,dailyTP 是时间、经度和纬度三个维度互相独立的网格数据吧,这个帖子想讨论的是经度和纬度不独立,二者会同时变化的一维散点数据,例如 flatten 后的卫星 L2 网格那种。
我试了下连续两次 groupby_bins,结果会是一维的,不符合预期。
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

发表于 5 天前 | 显示全部楼层
灭火器 发表于 2025-1-15 21:56
学到了,确实是秘方,量化到 0.1 分辨率再加一半的分辨率,相当于得到了分箱的标签,然后做 groupby,虽 ...

我凭印象盲打的,第二行里lons和lats一般我都会先处理成每个bin的中心便于画图。浮点数和非等距问题可以通过把第一行换成pd.cut解决,只不过对于均匀格点,尤其是1度等整数均匀格点,手动整除取整比cut更方便。

楼主有写公众号吗?方便的话加上我这个方法让我转载一下呗,提到“墨大宝”就行,我的公众号已经空了很久了。。。
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

发表于 5 天前 | 显示全部楼层
墨家大宝 发表于 2025-1-16 09:13
我凭印象盲打的,第二行里lons和lats一般我都会先处理成每个bin的中心便于画图。浮点数和非等距问题可以 ...

学习到了,非常感谢大佬们
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

 楼主| 发表于 5 天前 | 显示全部楼层
墨家大宝 发表于 2025-1-16 09:13
我凭印象盲打的,第二行里lons和lats一般我都会先处理成每个bin的中心便于画图。浮点数和非等距问题可以 ...

无公众号,可以给你投稿
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册 新浪微博登陆

本版积分规则

Copyright ©2011-2014 bbs.06climate.com All Rights Reserved.  Powered by Discuz! (京ICP-10201084)

本站信息均由会员发表,不代表气象家园立场,禁止在本站发表与国家法律相抵触言论

快速回复 返回顶部 返回列表