xqlsystems/xarray-sql

GitHub: xqlsystems/xarray-sql

一个实验性工具,通过将 Xarray 多维数组数据集转换为表格,使用户能够直接用 SQL 查询和科学计算大规模地球科学数据集。

Stars: 114 | Forks: 17

# xarray-sql _使用 SQL 查询 [Xarray](https://xarray.dev/)_ [![ci](https://static.pigsec.cn/wp-content/uploads/repos/cas/99/993938d8ce5e902ccfb9d6747725c320d855dea3235ed9a304cedf0d94c9321f.svg)](https://github.com/alxmrs/xarray-sql/actions/workflows/ci.yml) [![lint](https://static.pigsec.cn/wp-content/uploads/repos/cas/73/73ccbf4580efb36d6c888b7f4803d5e82c229071ca2fd1a481dac1a7a20547dc.svg)](https://github.com/alxmrs/xarray-sql/actions/workflows/lint.yml) [![ci-build](https://static.pigsec.cn/wp-content/uploads/repos/cas/2d/2dbcbcc0f0bd63e7666c1badf662d40980ba4a7a58c9e4d77e7e7f715e3fc3bd.svg)](https://github.com/alxmrs/xarray-sql/actions/workflows/ci-build.yml) [![ci-rust](https://static.pigsec.cn/wp-content/uploads/repos/cas/39/39d5e6752069447ccc0bd9dede20682564bd194d9b8ec7e2d8023fed9ffe951e.svg)](https://github.com/alxmrs/xarray-sql/actions/workflows/ci-rust.yml) ``` pip install xarray-sql ``` ## 这是什么? 这是一个为数组数据集提供 SQL 接口的实验。 简而言之,我们将 Xarray 数据集进行“透视”,把它们当作表格来处理,这样就可以对它们运行 SQL 查询了。 ## 快速入门 打开一个数据集,使用 `from_dataset` 将其注册为表格,在 SQL 中计算一个气候学统计结果,然后将结果写回 Xarray 并绘制图表: ``` import xarray as xr import xarray_sql as xql # 4x-daily surface air temperature on a lat/lon grid, 2013-2014. ds = xr.tutorial.open_dataset('air_temperature') ctx = xql.XarrayContext() ctx.from_dataset('air', ds, chunks=dict(time=100)) # A climatology — the mean annual cycle — computed in SQL: average air # temperature for each month of the year, over all grid cells and years. clim = ctx.sql(''' SELECT CAST(date_part('month', "time") AS INTEGER) AS month, AVG("air") AS air FROM "air" GROUP BY CAST(date_part('month', "time") AS INTEGER) ORDER BY month ''') # Round-trip the result back to Xarray. `month` is a derived column, so name # it as the dimension. clim_ds = clim.to_dataset(dims=["month"]) # Plot the annual cycle as a time series. clim_ds["air"].plot() # in a script, call matplotlib.pyplot.show() to display ``` 这就是一个完整的往返过程 —— 输入 Xarray,中间使用 SQL,输出 Xarray(以及图表)。 ## 一个更大的示例:ARCO-ERA5 同样的接口可以扩展到具有数百个变量的云原生数据集,例如 [ARCO-ERA5](https://github.com/google-research/arco-era5)。 ``` import xarray as xr import xarray_sql as xql # Open ARCO-ERA5 — a weather dataset with 273 variables since 1940. # Turning off dask means we don't have to wait to construct a task graph. ds = xr.open_zarr( 'gs://gcp-public-data-arco-era5/ar/full_37-1h-0p25deg-chunk-1.zarr-v3', chunks=None, # Turn dask off storage_options={'token': 'anon'} # Anonymous read from the public GCS bucket — no auth required. ) ctx = xql.XarrayContext() # Make sure to pass `chunks`! ctx.from_dataset('era5', ds, chunks=dict(time=6), table_names={ ('time', 'latitude', 'longitude'): 'surface', ('time', 'level', 'latitude', 'longitude'): 'atmosphere', }) # Registration takes ~10s on my machine. # Heads up: ARCO-ERA5 has 262 surface + 11 atmospheric variables. The library # pushes column projection down to Zarr, so SELECT only fetches what you ask # for — but `SELECT * FROM era5.surface` would try to pull every variable # across the year (terabytes from GCS). # ---> Always SELECT specific columns. <--- # Average 2m-temperature over NYC on the morning of 2020-01-01. The library # pushes WHERE clauses on dimension columns down to partition pruning. ctx.sql(''' SELECT AVG("2m_temperature") - 273.15 AS avg_c FROM era5.surface WHERE time BETWEEN TIMESTAMP '2020-01-01' AND TIMESTAMP '2020-01-01 05:00:00' AND latitude BETWEEN 39 AND 40 AND longitude BETWEEN 286 AND 287 -- ERA5 uses 0-360 longitudes ''').to_pandas() # avg_c # 0 8.640069 # Average temperature per pressure level, globally. result = ctx.sql(''' SELECT level, AVG(temperature) - 273.15 AS avg_c FROM era5.atmosphere WHERE time BETWEEN TIMESTAMP '2020-01-01' AND TIMESTAMP '2020-01-01 05:00:00' GROUP BY level ORDER BY level DESC ''') # DataFrame() # +-------+----------------------+ # | level | avg_c | # +-------+----------------------+ # | 1000 | 6.6210120796502565 | # | 975 | 5.185637919348153 | # | 950 | 4.028428657263021 | # | 925 | 3.0828117974912743 | # | 900 | 2.2109172992531967 | # | 875 | 1.395017610194202 | # | 850 | 0.6342670572626616 | # | 825 | -0.21037158786759846 | # | 800 | -1.1810754318269687 | # | 775 | -2.3064649711534457 | # +-------+----------------------+ ctx.sql(''' SELECT latitude, longitude, AVG("2m_temperature") - 273.15 AS avg_c FROM era5.surface WHERE time BETWEEN TIMESTAMP '2020-01-01' AND TIMESTAMP '2020-01-01 05:00:00' GROUP BY latitude, longitude ORDER BY latitude DESC, longitude # `latitude`/`longitude` are inferred from the registered table's surviving # dims; `template` is kept only to recover metadata (attrs, encoding). ''').to_dataset(template=ds) # Size: 8MB # Dimensions: (latitude: 721, longitude: 1440) # Coordinates: # * latitude (latitude) float32 3kB 90.0 89.75 89.5 ... -89.5 -89.75 -90.0 # * longitude (longitude) float32 6kB 0.0 0.25 0.5 0.75 ... 359.2 359.5 359.8 # Data variables: # avg_c (latitude, longitude) float64 8MB -26.84 -26.84 ... -27.38 -27.38 # Attributes: # last_updated: 2026-06-20 02:33:34.265980+00:00 # valid_time_start: 1940-01-01 # valid_time_stop: 2025-12-31 # valid_time_stop_era5t: 2026-06-14 ``` _(此示例的可运行版本位于 [`perf_tests/era5_temp_profile.py`](perf_tests/era5_temp_profile.py)。)_ ## 为什么要开发这个? 有几个原因: * 尽管 SQL 是数据领域的通用语言,但非科学家(SQL 用户)通常无法访问科学数据集。 * 将表格数据与栅格数据进行连接(JOIN)很常见,但也十分困难。其实这件事可以很简单。 * 世界上有许多云原生、可由 Xarray 打开的数据集, 从 [Google Earth Engine](https://github.com/google/Xee) 到 [Source Cooperative](https://source.coop/products?tags=zarr)。如果这些数据集也能通过 SQL 访问,岂不是很好?如何以最小的代价搭建起这座桥梁? 这是一种证明该接口价值的轻量级方式。 更大的目标是探索这样一个假设:[Pangeo 生态系统是一个科学数据库](https://www.hytradboi.com/2025/c18b8cdc-fd17-4099-9c03-eb107217f627-pangeo-is-a-database)。在这里,xarray-sql 可以被看作是缺失的数据库前端。 ## 它是如何工作的? Xarray 数据集中的所有 chunk 都会通过 `from_map()` 和 `to_dataframe()` 转换为 Dask DataFrame。至于 SQL 支持,我们只需使用 `dask-sql` 即可。就这么简单! _2025 年更新_:该库现在以纯 DataFusion 和 PyArrow 实现了一个类似 Dask 的 `from_map` 接口,但其工作原理是一样的! _2026 年更新_:我们不再使用 `from_map()`,而是创建了一种将 Xarray chunk 转换为 Arrow RecordBatches 的方法。我们将一个 Python 回调函数传递给 DataFusion 的 `TableProvider`,让数据库引擎将底层数据集数组转换为 DataFusion 分区。归根结底,`pivot()` 函数的最初洞察——即任何 ndarray 都可以转换为二维表格——正是这种高效查询机制的基础。 ## 它真的可用吗? 是的。人们反复担心的一个问题是,SQL 接口只是个玩具——能应付简单的 `SELECT`,但对于地球科学实际运行的操作却无能为力。因此,我们编写了一套测试用例,提取了地理空间和气候分析中的基础操作(那些我们*认为必须*使用数组库的操作),将每一个都用 SQL 表示,然后**将 SQL 计算出的结果与 xarray/array 的参考结果进行对比**,确保误差在浮点数精度范围内: * **光谱指数**(NDVI)——针对真实的 Sentinel-2 场景进行列算术运算。 * **气候学统计、异常、纬向平均**——在作为惰性表注册的 0.25° **ARCO-ERA5** 归档数据上执行 `GROUP BY` 和自 `JOIN`。每个查询都限定在一个小的时间空间窗口内(某个区域内的几天)并且只读取那部分切片——这证明了你可以将查询指向一个长达数十年的数据档案,且只为查询请求的数据付费,而不是去扫描整个记录。 * **预报准确性**——使用 `JOIN`(基于 `valid_time = init + lead`)评估 **Pangu-Weather** 和 **GraphCast** ML 模型相对于 ERA5(WeatherBench 2)的准确性;它重现了已发表的研究结果,即 GraphCast 在所有预报时效(lead time)上均优于 Pangu。 * **栅格 × 矢量分区统计**——将 ERA5 网格与区域表进行范围 `JOIN`。 * **重投影和重网格化**——一个标量 PROJ UDF(通过 [Xee](https://github.com/google/Xee) 对照 Earth Engine 自身的测地学功能进行了验证)以及一个稀疏权重表 `JOIN`(对真实的 SRTM 地形进行重网格化)。 每一个用例的计算结果都与其对应的数组参考一致。核心发现是:这些操作实际上根本不是真正的“数组”操作——它们不过是伪装起来的 `GROUP BY`、`JOIN`、窗口函数和 `CASE`,而查询引擎完全能够大规模运行它们。详见 [`benchmarks/geospatial/`](benchmarks/geospatial/) 和这篇说明文章 [地理空间操作即关系操作](docs/geospatial.md)。 ## 为什么这会奏效? 在 Xarray、Dask 和 Pandas 的底层,使用的都是 NumPy 数组。这些数组按 chunk 分页,并在内存中连续存放。将它们拆分为 ndarray 的仅仅是元数据的差异。`pivot()`(通过 `to_dataframe()`)只是改变了这些元数据(通过 `ravel()`/`reshape()`),使其重新变成适合 DataFrame 的列。我们利用这种轻量级的元数据转换,让数据库引擎(DataFusion)能够扫描分块信息。 ## 当前的局限性是什么? 待定,DataFusion 开启了一个全新的世界!目前,我们正在寻找早期用户——或者可以说是“试水者”。我们非常乐意听取您的意见,以塑造本项目的发展方向!请务必试一试,并随时[提交 issue](https://github.com/alxmrs/xarray-sql/issues)。也请查看我们的[贡献指南](CONTRIBUTING.md) 😉。 ## 更深度的集成会是什么样子? 目前我已经有一些想法了。其中一种方法是直接在 Xarray 数据集上应用操作。这种方法正在 `xql` 项目中被采用和推进,详见[这里](https://github.com/google/weather-tools/tree/main/xql)。 更进一步的思考:我认为我们可以为 parquet 创建一个[虚拟](https://fsspec.github.io/kerchunk/)文件系统,在内部将其映射到 Zarr。基于栅格的虚拟 parquet 将开启与 dask、pyarrow、duckdb 和 BigQuery 等众多工具的集成。更多关于此的想法请见 [#4](https://github.com/alxmrs/xarray-sql/issues/4)。 _2025 年更新_:类似的东西正在几个项目中被构建起来!我所知道的有: - [CartoDB 的 Raquet](https://github.com/CartoDB/raquet) - DataFusion 社区的 [arrow-zarr](https://github.com/datafusion-contrib/arrow-zarr) _2026 年更新_:我和一位同事正在尝试使用原生的 Zarr RDBMS 引擎。请关注: - [Zarr-Datafusion](https://lib.rs/crates/zarr-datafusion) - [DuckDB-Zarr](https://github.com/alxmrs/duckdb-zarr) ## 路线图 - [x] ~通过 pyarrow Dataset 接口实现惰性求值 [#93](https://github.com/alxmrs/xarray-sql/issues/93)。~ _已在 [#100](https://github.com/alxmrs/xarray-sql/pull/100) 中实现_ - [x] 在 rust/datafusion 端通过正确的分区处理来支持适当的并行机制。[#106](https://github.com/alxmrs/xarray-sql/issues/106) - [x] 支持核心的 DataFusion 优化以扫描更少的数据,例如 [104](https://github.com/alxmrs/xarray-sql/issues/104), ... - [x] 将单个 Zarr 转换为表的集合 [#85](https://github.com/alxmrs/xarray-sql/issues/85)。 - [ ] 通过 DataFusion 与 Ray Datasets [#68](https://github.com/alxmrs/xarray-sql/issues/68) 或 Apache Ballista [#98](https://github.com/alxmrs/xarray-sql/issues/98) 的集成,实现跨越单节点的分布式计算。 - [ ] 演示:在 SQL 中计算从 1940 年至今的海面温度(Sea Surface Temperature)[#36](https://github.com/alxmrs/xarray-sql/issues/36)。 - [ ] 提供通过 Rust 将 DataFusion 直接与 Zarr 集成的选项 [#4](https://github.com/alxmrs/xarray-sql/issues/4)。 - [ ] (最终将正式公布):100 万亿行数据挑战赛(The 100 Trillion Row Challenge) [#34](https://github.com/alxmrs/xarray-sql/issues/34)。 ## 许可证 ``` Copyright 2024 Alexander Merose Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` 所有引入的第三方代码(vendored code)均具有正确的许可证归属。
标签:Python, SQL, Xarray, 可视化界面, 数据查询, 数据科学, 无后门, 科学计算, 系统审计, 资源验证, 逆向工具