diff --git a/README.md b/README.md index d991aee..290b7a3 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ In [partnership with NOAA](https://techpartnerships.noaa.gov/tpo_partnership/mak ## Data NNJA datasets are organized by sensor/source (e.g. all-sky radiances from the GOES ABI). -The list of all NNJA datasets can be found on the [NNJA project page](https://psl.noaa.gov/data/nnja_obs/#data-sources), while the subset that is currently found in the NNJA-AI archive can be found [here](docs/datasets.md) or by exploring the data catalog (this will be be expanding rapidly). +The list of all NNJA datasets can be found on the [NNJA project page](https://psl.noaa.gov/data/nnja_obs/#data-sources), while the subset that is currently found in the NNJA-AI archive can be found [here](datasets.md) or by exploring the data catalog (this will be be expanding rapidly). ## Getting Started diff --git a/docs/datasets.md b/docs/datasets.md index ea1ba5a..cbe97f8 100644 --- a/docs/datasets.md +++ b/docs/datasets.md @@ -114,7 +114,7 @@ dataset = dataset.sel(variables=primary_vars) dataset.variables ``` -Some fields have complex meanings; be sure to read [Understanding the Data](/docs/understanding-the-data.md). +Some fields have complex meanings; be sure to read [Understanding the Data](understanding-the-data.md). #### ADPSFC - Synoptic Fixed Land (from WMO SYNOP bulletins) @@ -123,7 +123,7 @@ Some fields have complex meanings; be sure to read [Understanding the Data](/doc - Message: `NC000001` - Dates processed: `1979-01-01 to 2025-03-31` - Surface data from fixed land stations, received through WMO SYNOP bulletins. This dataset and [NC000101](/docs/datasets.md#adpsfc---synoptic-fixed-land-originally-in-bufr) come from the same sources; the WMO SYNOP bulletins are legacy reporting messages while the BUFR messages are from stations that have migrated to the newer report type. At the current time, most stations have migrated to BUFR. + Surface data from fixed land stations, received through WMO SYNOP bulletins. This dataset and [NC000101](#adpsfc-synoptic-fixed-land-originally-in-bufr) come from the same sources; the WMO SYNOP bulletins are legacy reporting messages while the BUFR messages are from stations that have migrated to the newer report type. At the current time, most stations have migrated to BUFR. #### ADPSFC - Synoptic Mobile Land (from WMO SYNOP MOBIL bulletins) @@ -151,7 +151,7 @@ Surface data from aviation weather reports (METAR/SPECI). This dataset contains - Message: `NC000101` - Dates processed: `2020-10-22 to 2025-03-31` -Surface data from fixed land stations, originally received in BUFR format. This dataset and [NC000001](/docs/datasets.md#adpsfc---synoptic-fixed-land-from-wmo-synop-bulletins) come from the same sources; the WMO SYNOP bulletins are legacy reporting messages while the BUFR messages are from stations that have migrated to the newer report type. At the current time, most stations have migrated to BUFR. +Surface data from fixed land stations, originally received in BUFR format. This dataset and [NC000001](#adpsfc-synoptic-fixed-land-from-wmo-synop-bulletins) come from the same sources; the WMO SYNOP bulletins are legacy reporting messages while the BUFR messages are from stations that have migrated to the newer report type. At the current time, most stations have migrated to BUFR. #### Additional Resources @@ -167,7 +167,7 @@ Surface data from fixed land stations, originally received in BUFR format. This - Message: `NC002001` - Dates processsed: `2009-12-31 to 2025-03-31` -Upper-air rawinsonde (radiosonde) data. This is a sparse dataset with coverage over land only. The main data packet is temperature, dew point temperature, wind speed, and wind direction, and geopotential, on pressure levels (columns with prefixes, in order, `TMDB_`, `TMDP_`, `WSPD_`, `WDIR_`, `GP10_`). These data are originally packaged into the `UARLV` column, however their structure does not lend itself well to flattening. The UPA profile plotting [example notebook](/example_notebooks/adpupa_profile_example.ipynb) demonstrates some of the nuances of accessing this data. +Upper-air rawinsonde (radiosonde) data. This is a sparse dataset with coverage over land only. The main data packet is temperature, dew point temperature, wind speed, and wind direction, and geopotential, on pressure levels (columns with prefixes, in order, `TMDB_`, `TMDP_`, `WSPD_`, `WDIR_`, `GP10_`). These data are originally packaged into the `UARLV` column, however their structure does not lend itself well to flattening. The UPA profile plotting [example notebook](example_notebooks/adpupa_profile_example.ipynb) demonstrates some of the nuances of accessing this data. #### Additional Resources - [BUFR source data on AWS](https://noaa-reanalyses-pds.s3.amazonaws.com/index.html#observations/reanalysis/conv/convbufr/adpupa/) diff --git a/docs/example_notebooks b/docs/example_notebooks new file mode 120000 index 0000000..9906a97 --- /dev/null +++ b/docs/example_notebooks @@ -0,0 +1 @@ +../example_notebooks \ No newline at end of file diff --git a/docs/faq.md b/docs/faq.md index bfe7b78..bdd0176 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,34 +1,42 @@ # Frequently Asked Questions -- [What is the status of this archive?](#what-is-the-status-of-this-archive) -- [What datasets do you have?](#what-datasets-do-you-have) -- [What format is the data in and where is it stored?](#what-format-is-the-data-in-and-where-is-it-stored) -- [Why is are some columns still a structured field?](#why-is-are-some-columns-still-a-structured-field) -- [Why is this entire column missing data?](#why-is-this-entire-column-missing-data) -- [Why didn't you use zarr instead of parquet?](#why-didn-t-you-use-zarr-instead-of-parquet) -- [There are too many columns! How do I filter them?](#there-are-too-many-columns-how-do-i-filter-them) -- [Will you keep the datasets current with the most recent data?](#will-you-keep-the-datasets-current-with-the-most-recent-data) -- [How do I get in touch with you?](#how-do-i-get-in-touch-with-you) +- [Frequently Asked Questions](#frequently-asked-questions) + - [What is the status of this archive?](#what-is-the-status-of-this-archive) + - [What datasets do you have?](#what-datasets-do-you-have) + - [What format is the data in and where is it stored?](#what-format-is-the-data-in-and-where-is-it-stored) + - [What have you done to process the NNJA BUFR files?](#what-have-you-done-to-process-the-nnja-bufr-files) + - [Why are some columns still a structured field?](#why-are-some-columns-still-a-structured-field) + - [Why didn't you use zarr instead of parquet?](#why-didnt-you-use-zarr-instead-of-parquet) + - [There are too many columns! How do I filter them?](#there-are-too-many-columns-how-do-i-filter-them) + - [What are code and flag table variables?](#what-are-code-and-flag-table-variables) + - [Why is this or that entire column missing data?](#why-is-this-or-that-entire-column-missing-data) + - [Will you keep the datasets current with the most recent data?](#will-you-keep-the-datasets-current-with-the-most-recent-data) + - [How do I get in touch with you?](#how-do-i-get-in-touch-with-you) ## What is the status of this archive? + The NNJA-AI v1 release contains all the data currently processed to parquet form at time of writing (Aug 2025). Data processing from the NNJA archive is ongoing at Brightband. New datasets will be added periodically. ## What datasets do you have? -All datasets currently available are listed in the [datasets documentation](/docs/datasets.md). + +All datasets currently available are listed in the [datasets documentation](datasets.md). We are working to add more datasets to the archive, and will update the documentation as we add more datasets. ## What format is the data in and where is it stored? + The data is stored on GCS in parquets with partitions for each day. -See the example notebook [here](/example_notebooks/basic_dataset_example.ipynb) for a guide on how to access the data. +See the example notebook [here](example_notebooks/basic_dataset_example.ipynb) for a guide on how to access the data. If you prefer to bypass this SDK, you can currently find the v1 datasets here: `gs://nnja-ai/data/v1/` for direct access to the parquet files. ## What have you done to process the NNJA BUFR files? + 1) convert from BUFR to AVRO, preserving all the structure of the original BUFR messages (nested types, etc.). 2) 'flatten' the complex columns (array, struct) into simple scalar columns. 3) combined 6-hourly files into daily Parquet partitions based on the observation timestamp. ## Why are some columns still a structured field? + The original BUFR data is highly structured, with multiple levels of nested data. While we have flattened the data as much as possible, there are some cases where the data is still structured. This is either because the original data could not be flattened @@ -36,7 +44,9 @@ This is either because the original data could not be flattened or because the data is not worth flattening (e.g. because all subcolumns are null-valued). ## Why didn't you use zarr instead of parquet? + We considered using zarr as the underlying storage format, but decided to use parquet for a few reasons: + - The original BUFR data is inherently tabular, and parquet is a good format for tabular data. - Generally the data is one-dimensional (time), and zarr's strength is in multi-dimensional data. There are some datasets that have some additional dimensions (e.g. channel for satellite data), @@ -44,6 +54,7 @@ but we decided to stick with one format for simplicity. - There are many tools and libraries that support parquet (e.g. pandas, polars, dask, BigQuery, etc.) ## There are too many columns! How do I filter them? + We have classified the columns into the following categories, based on our understanding of the data: `primary data`, `primary descriptors`, `secondary data`, `secondary descriptors`. You can quickly filter the columns with a snippet like the following: @@ -54,6 +65,7 @@ dataset = dataset.sel(variables=primary_vars) ``` ## What are code and flag table variables? + Some variables are encoded as a code table or flag table. We're working on adding a helper utility to incorporate these into the archive and decode them, but in the meantime you can use the Variable.info() method to view the code table or flag table to link to the NOAA code table page, or look them up directly here: https://www.nco.ncep.noaa.gov/sib/jeff/CodeFlag_0_STDv31_LOC7.html @@ -72,9 +84,10 @@ ds.variables['SIDENSEQ.SIDGRSEQ.SAID'].extra_metadata ## Why is this or that entire column missing data? + We included all data fields from the original BUFR data. Some data fields were not populated in the original data (e.g. GOES warm channels are not present in the ABI data). -If the [datasets documentation](/docs/datasets.md) does not mention a missing field that you think should be present, +If the [datasets documentation](datasets.md) does not mention a missing field that you think should be present, please raise an issue on [GitHub](https://github.com/brightbandtech/nnja-ai/issues) and we will check with the data providers and update the documentation (or if you check, please let us know!). diff --git a/example_notebooks/adpsfc_example.ipynb b/example_notebooks/adpsfc_example.ipynb index 230de41..e491b26 100644 --- a/example_notebooks/adpsfc_example.ipynb +++ b/example_notebooks/adpsfc_example.ipynb @@ -4,17 +4,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/brightbandtech/nnja-ai/blob/main/example_notebooks/adpsfc_example.ipynb)" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "# Uncomment the following line to install the package\n", - "#!pip install git+https://github.com/brightbandtech/nnja-ai.git" + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/brightbandtech/nnja-ai/blob/main/example_notebooks/adpsfc_example.ipynb)\n", + "\n", + "On Colab, run the following command in a Python cell.\n", + "\n", + "``` sh\n", + "!pip install git+https://github.com/brightbandtech/nnja-ai.git\n", + "```" ] }, { @@ -22,6 +18,7 @@ "metadata": {}, "source": [ "## Navigating APDSFC data\n", + "\n", "The ADPSFC datasets, representing surface station observations, are very rich in data, but as a result, a bit difficult to dig through. Here we'll shows a few ways to explore the dataset and find the most useful variables" ] }, @@ -144,7 +141,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Subsetting to primary variables\n", + "## Subsetting to primary variables\n", + "\n", "A dataset like ADPSFC can have useful variables (such as measured air temperature, \"TMPSQ1.TMDB\"), along with less useful ones (such as the usually unreported estimated rate of ice accretion, \"ICESQ1.ROIA\"). Similarly, there are key descriptor fields (e.g. LAT and LON) and less valuable descriptor fields (e.g. \"TMPSQ1.MSST\", the method of water temperature and/or salinity measurement). All of these fields are included in the NNJA-AI dataset, but a quick way to subset the variables to explore is to use the 'category' field, which we have added to help filter out some of these less salient fields." ] }, @@ -390,11 +388,11 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], - "source": [] + "source": [ + "Plot a map of temperatures for a day" + ] }, { "cell_type": "code", @@ -402,7 +400,9 @@ "metadata": {}, "outputs": [], "source": [ - "# plot map of temperatures for a day" + "import matplotlib.pyplot as plt\n", + "\n", + "plt.scatter(x=\"LON\", Y=\"LAT\", c=\"TMPSQ1.TMDB\", data=ds)" ] } ], diff --git a/example_notebooks/adpupa_profile_example.ipynb b/example_notebooks/adpupa_profile_example.ipynb index e1894ed..52d4615 100644 --- a/example_notebooks/adpupa_profile_example.ipynb +++ b/example_notebooks/adpupa_profile_example.ipynb @@ -4,17 +4,21 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/brightbandtech/nnja-ai/blob/main/example_notebooks/adpupa_profile_example.ipynb)" + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/brightbandtech/nnja-ai/blob/main/example_notebooks/adpupa_profile_example.ipynb)\n", + "\n", + "On Colab, run the following command in a Python cell.\n", + "\n", + "``` sh\n", + "!pip install git+https://github.com/brightbandtech/nnja-ai.git\n", + "```" ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# Uncomment the following line to install the package\n", - "#!pip install git+https://github.com/brightbandtech/nnja-ai.git\n", "import pandas as pd\n", "import numpy as np\n", "import matplotlib.pyplot as plt" diff --git a/example_notebooks/basic_dataset_example.ipynb b/example_notebooks/basic_dataset_example.ipynb index 8e3c738..43b78cd 100644 --- a/example_notebooks/basic_dataset_example.ipynb +++ b/example_notebooks/basic_dataset_example.ipynb @@ -4,17 +4,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/brightbandtech/nnja-ai/blob/main/example_notebooks/basic_dataset_example.ipynb)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Uncomment the following line to install the package\n", - "#!pip install git+https://github.com/brightbandtech/nnja-ai.git" + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/brightbandtech/nnja-ai/blob/main/example_notebooks/basic_dataset_example.ipynb)\n", + "\n", + "On Colab, run the following command in a Python cell.\n", + "\n", + "``` sh\n", + "!pip install git+https://github.com/brightbandtech/nnja-ai.git\n", + "```" ] }, { @@ -402,13 +398,6 @@ "df = amsu_ds.load_dataset(backend=\"pandas\")\n", "plot_df(df, \"BRITCSTC.TMBR_00001\")" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/example_notebooks/dataset_overview.ipynb b/example_notebooks/dataset_overview.ipynb index d894360..8d1e0a2 100644 --- a/example_notebooks/dataset_overview.ipynb +++ b/example_notebooks/dataset_overview.ipynb @@ -1,5 +1,19 @@ { "cells": [ + { + "cell_type": "markdown", + "id": "04f71471", + "metadata": {}, + "source": [ + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/brightbandtech/nnja-ai/blob/main/example_notebooks/dataset_overview.ipynb)\n", + "\n", + "On Colab, run the following command in a Python cell.\n", + "\n", + "``` sh\n", + "!pip install git+https://github.com/brightbandtech/nnja-ai.git\n", + "```" + ] + }, { "cell_type": "markdown", "id": "ea4cd315", @@ -203,14 +217,6 @@ "source": [ "ds.manifest.index" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b90391aa", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/mkdocs.yml b/mkdocs.yml index f2675d9..e0fb8cf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,6 +21,7 @@ extra_css: plugins: - search + - mkdocs-jupyter - mkdocs-autoapi: autoapi_dir: src - mkdocstrings: @@ -45,4 +46,10 @@ nav: - Data: - Datasets: datasets.md - Understanding the Data: understanding-the-data.md + - Example Notebooks: + - Basic Dataset Example: example_notebooks/basic_dataset_example.ipynb + - Dataset Overview: example_notebooks/dataset_overview.ipynb + - ADPSFC (weather station) Example: example_notebooks/adpsfc_example.ipynb + - ADPUPA (radiosonde) Profile Example: example_notebooks/adpupa_profile_example.ipynb + - FAQ: faq.md diff --git a/pyproject.toml b/pyproject.toml index 990b110..0753934 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,6 +64,7 @@ docs = [ "mkdocstrings[python]>=0.29.1", "mkdocs-autoapi[python]>=0.4.1", "mkdocs-material>=9.6.14", + "mkdocs-jupyter>=0.25.1", ] interactive = [ "ipykernel", diff --git a/uv.lock b/uv.lock index 3a28399..93d5833 100644 --- a/uv.lock +++ b/uv.lock @@ -1693,6 +1693,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl", hash = "sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4", size = 59700 }, ] +[[package]] +name = "jupytext" +version = "1.17.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "mdit-py-plugins" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/ce/0bd5290ca4978777154e2683413dca761781aacf57f7dc0146f5210df8b1/jupytext-1.17.2.tar.gz", hash = "sha256:772d92898ac1f2ded69106f897b34af48ce4a85c985fa043a378ff5a65455f02", size = 3748577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/f1/82ea8e783433707cafd9790099a2d19f113c22f32a31c8bb5abdc7a61dbb/jupytext-1.17.2-py3-none-any.whl", hash = "sha256:4f85dc43bb6a24b75491c5c434001ad5ef563932f68f15dd3e1c8ce12a4a426b", size = 164401 }, +] + [[package]] name = "keyring" version = "25.6.0" @@ -1980,6 +1997,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 }, ] +[[package]] +name = "mdit-py-plugins" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/03/a2ecab526543b152300717cf232bb4bb8605b6edb946c845016fa9c9c9fd/mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5", size = 43542 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/f7/7782a043553ee469c1ff49cfa1cdace2d6bf99a1f333cf38676b3ddf30da/mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636", size = 55316 }, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -2080,6 +2109,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521 }, ] +[[package]] +name = "mkdocs-jupyter" +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel" }, + { name = "jupytext" }, + { name = "mkdocs" }, + { name = "mkdocs-material" }, + { name = "nbconvert" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/23/6ffb8d2fd2117aa860a04c6fe2510b21bc3c3c085907ffdd851caba53152/mkdocs_jupyter-0.25.1.tar.gz", hash = "sha256:0e9272ff4947e0ec683c92423a4bfb42a26477c103ab1a6ab8277e2dcc8f7afe", size = 1626747 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl", hash = "sha256:3f679a857609885d322880e72533ef5255561bbfdb13cfee2a1e92ef4d4ad8d8", size = 1456197 }, +] + [[package]] name = "mkdocs-material" version = "9.6.16" @@ -2438,6 +2484,7 @@ dask = [ docs = [ { name = "mkdocs" }, { name = "mkdocs-autoapi", extra = ["python"] }, + { name = "mkdocs-jupyter" }, { name = "mkdocs-material" }, { name = "mkdocstrings", extra = ["python"] }, ] @@ -2476,6 +2523,7 @@ requires-dist = [ { name = "matplotlib", marker = "extra == 'interactive'" }, { name = "mkdocs", marker = "extra == 'docs'", specifier = ">=1.6.1" }, { name = "mkdocs-autoapi", extras = ["python"], marker = "extra == 'docs'", specifier = ">=0.4.1" }, + { name = "mkdocs-jupyter", marker = "extra == 'docs'", specifier = ">=0.25.1" }, { name = "mkdocs-material", marker = "extra == 'docs'", specifier = ">=9.6.14" }, { name = "mkdocstrings", extras = ["python"], marker = "extra == 'docs'", specifier = ">=0.29.1" }, { name = "nnja-ai", extras = ["interactive", "dask", "polars"], marker = "extra == 'complete'", editable = "." },