Skip to content

merra2

MERRA-2 Reader

MERRA2Reader

Bases: GriddedReader

Reader for MERRA-2 (Modern-Era Retrospective analysis for Research and Applications, Version 2) data.

Source code in monetio/readers/merra2.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
@register_reader("merra2")
class MERRA2Reader(GriddedReader):
    """
    Reader for MERRA-2 (Modern-Era Retrospective analysis for Research and Applications, Version 2) data.
    """

    def open_dataset(
        self,
        files: str | list[str] | None = None,
        dates: pd.DatetimeIndex | list[datetime.datetime] | datetime.datetime | str | None = None,
        product: str = "inst1_2d_asm_Nx",
        username: str | None = None,
        password: str | None = None,
        virtualizarr: str | None = None,
        **kwargs,
    ) -> xr.Dataset:
        """
        Reads MERRA-2 data.

        Parameters
        ----------
        files : Union[str, List[str]], optional
            File path(s) or URL(s). If None, will try to build URLs using dates and product.
        dates : Union[pd.DatetimeIndex, List[datetime], datetime, str], optional
            Dates to retrieve. Used if files is None.
        product : str, optional
            MERRA-2 product short name, by default "inst1_2d_asm_Nx".
            Common products:
            - 'inst1_2d_asm_Nx': Instantaneous 2D atmospheric fields
            - 'tavg1_2d_slv_Nx': Time-averaged 2D surface fields
            - 'inst3_3d_asm_Np': Instantaneous 3D atmospheric fields (pressure levels)
            - 'inst3_3d_chm_Np': Instantaneous 3D chemical fields (pressure levels)
        username : str, optional
            NASA Earthdata username. If provided, will setup .netrc.
        password : str, optional
            NASA Earthdata password. If provided, will setup .netrc.
        **kwargs : dict
            Additional arguments passed to XarrayDriver.open.

        Returns
        -------
        xr.Dataset
            The MERRA-2 dataset.
        """
        if username and password:
            setup_netrc(username, password)

        if files is None:
            if dates is None:
                raise ValueError("Either 'files' or 'dates' must be provided.")
            files = self.build_urls(dates, product=product)

        if "preprocess" not in kwargs:
            from functools import partial

            kwargs["preprocess"] = partial(merra2_preprocess, product=product)

        if virtualizarr is not None:
            kwargs["virtualizarr_file"] = virtualizarr

        ds = super().open_dataset(files, **kwargs)

        # Update history
        ds = update_history(ds, f"Read MERRA-2 {product} data.")

        return ds

    def build_urls(
        self,
        dates: pd.DatetimeIndex | list[datetime.datetime] | datetime.datetime | str,
        product: str = "inst1_2d_asm_Nx",
    ) -> list[str]:
        """
        Build OPeNDAP URLs for MERRA-2 data based on dates and product.

        Parameters
        ----------
        dates : Union[pd.DatetimeIndex, List[datetime], datetime, str]
            Dates to retrieve.
        product : str, optional
            MERRA-2 product short name.

        Returns
        -------
        List[str]
            List of OPeNDAP URLs.
        """
        if isinstance(dates, str | datetime.datetime | pd.Timestamp):
            dates = pd.DatetimeIndex([pd.to_datetime(dates)])
        else:
            dates = pd.to_datetime(dates)

        # Product mapping to GES DISC OPeNDAP paths
        # Format: (ShortName.Version, CollectionName, ServerNumber)
        prod_map = {
            "inst1_2d_asm_Nx": ("M2I1NXASM.5.12.4", "inst1_2d_asm_Nx", "4"),
            "inst1_2d_int_Nx": ("M2I1NXINT.5.12.4", "inst1_2d_int_Nx", "4"),
            "inst1_2d_lfo_Nx": ("M2I1NXLFO.5.12.4", "inst1_2d_lfo_Nx", "4"),
            "inst3_2d_gas_Nx": ("M2I3NXGAS.5.12.4", "inst3_2d_gas_Nx", "4"),
            "statD_2d_slv_Nx": ("M2SDNXSLV.5.12.4", "statD_2d_slv_Nx", "4"),
            "tavg1_2d_adg_Nx": ("M2T1NXADG.5.12.4", "tavg1_2d_adg_Nx", "4"),
            "tavg1_2d_aer_Nx": ("M2T1NXAER.5.12.4", "tavg1_2d_aer_Nx", "4"),
            "tavg1_2d_chm_Nx": ("M2T1NXCHM.5.12.4", "tavg1_2d_chm_Nx", "4"),
            "tavg1_2d_csp_Nx": ("M2T1NXCSP.5.12.4", "tavg1_2d_csp_Nx", "4"),
            "tavg1_2d_flx_Nx": ("M2T1NXFLX.5.12.4", "tavg1_2d_flx_Nx", "4"),
            "tavg1_2d_int_Nx": ("M2T1NXINT.5.12.4", "tavg1_2d_int_Nx", "4"),
            "tavg1_2d_lfo_Nx": ("M2T1NXLFO.5.12.4", "tavg1_2d_lfo_Nx", "4"),
            "tavg1_2d_lnd_Nx": ("M2T1NXLND.5.12.4", "tavg1_2d_lnd_Nx", "4"),
            "tavg1_2d_ocn_Nx": ("M2T1NXOCN.5.12.4", "tavg1_2d_ocn_Nx", "4"),
            "tavg1_2d_rad_Nx": ("M2T1NXRAD.5.12.4", "tavg1_2d_rad_Nx", "4"),
            "tavg1_2d_slv_Nx": ("M2T1NXSLV.5.12.4", "tavg1_2d_slv_Nx", "4"),
            "tavg3_2d_glc_Nx": ("M2T3NXGLC.5.12.4", "tavg3_2d_glc_Nx", "4"),
            "inst3_3d_asm_Np": ("M2I3NPASM.5.12.4", "inst3_3d_asm_Np", "5"),
            "inst3_3d_aer_Nv": ("M2I3NVAER.5.12.4", "inst3_3d_aer_Nv", "5"),
            "inst3_3d_asm_Nv": ("M2I3NVASM.5.12.4", "inst3_3d_asm_Nv", "5"),
            "inst3_3d_chm_Nv": ("M2I3NVCHM.5.12.4", "inst3_3d_chm_Nv", "5"),
            "inst3_3d_gas_Nv": ("M2I3NVGAS.5.12.4", "inst3_3d_gas_Nv", "5"),
            "inst6_3d_ana_Np": ("M2I6NPANA.5.12.4", "inst6_3d_ana_Np", "5"),
            "inst6_3d_ana_Nv": ("M2I6NVANA.5.12.4", "inst6_3d_ana_Nv", "5"),
            "tavg3_3d_mst_Ne": ("M2T3NEMST.5.12.4", "tavg3_3d_mst_Ne", "5"),
            "tavg3_3d_nav_Ne": ("M2T3NENAV.5.12.4", "tavg3_3d_nav_Ne", "5"),
            "tavg3_3d_trb_Ne": ("M2T3NETRB.5.12.4", "tavg3_3d_trb_Ne", "5"),
            "tavg3_3d_cld_Np": ("M2T3NPCLD.5.12.4", "tavg3_3d_cld_Np", "5"),
            "tavg3_3d_mst_Np": ("M2T3NPMST.5.12.4", "tavg3_3d_mst_Np", "5"),
            "tavg3_3d_odt_Np": ("M2T3NPODT.5.12.4", "tavg3_3d_odt_Np", "5"),
            "tavg3_3d_qdt_Np": ("M2T3NPQDT.5.12.4", "tavg3_3d_qdt_Np", "5"),
            "tavg3_3d_rad_Np": ("M2T3NPRAD.5.12.4", "tavg3_3d_rad_Np", "5"),
            "tavg3_3d_tdt_Np": ("M2T3NPTDT.5.12.4", "tavg3_3d_tdt_Np", "5"),
            "tavg3_3d_trb_Np": ("M2T3NPTRB.5.12.4", "tavg3_3d_trb_Np", "5"),
            "tavg3_3d_udt_Np": ("M2T3NPUDT.5.12.4", "tavg3_3d_udt_Np", "5"),
            "tavg3_3d_asm_Nv": ("M2T3NVASM.5.12.4", "tavg3_3d_asm_Nv", "5"),
            "tavg3_3d_cld_Nv": ("M2T3NVCLD.5.12.4", "tavg3_3d_cld_Nv", "5"),
            "tavg3_3d_mst_Nv": ("M2T3NVMST.5.12.4", "tavg3_3d_mst_Nv", "5"),
            "tavg3_3d_rad_Nv": ("M2T3NVRAD.5.12.4", "tavg3_3d_rad_Nv", "5"),
            "inst3_3d_chm_Np": ("M2I3NPCHM.5.12.4", "inst3_3d_chm_Np", "5"),
        }

        if product not in prod_map:
            raise ValueError(f"Unknown product: {product}. Available: {list(prod_map.keys())}")

        short_name, coll_name, server = prod_map[product]
        base_url = f"https://goldsmr{server}.gesdisc.eosdis.nasa.gov/opendap/MERRA2/{short_name}"

        urls = []
        for d in dates.floor("D").unique():
            # MERRA-2 filenames usually include the date and the product name
            # Example: MERRA2_400.inst1_2d_asm_Nx.20240101.nc4
            # The stream ID (400, 300, 200, 100) depends on the year.
            year = d.year
            if 1980 <= year <= 1991:
                stream = "100"
            elif 1992 <= year <= 2000:
                stream = "200"
            elif 2001 <= year <= 2010:
                stream = "300"
            else:
                stream = "400"

            date_str = d.strftime("%Y%m%d")
            url = f"{base_url}/{d.strftime('%Y/%m')}/MERRA2_{stream}.{coll_name}.{date_str}.nc4"
            urls.append(url)

        return urls

build_urls(dates, product='inst1_2d_asm_Nx')

Build OPeNDAP URLs for MERRA-2 data based on dates and product.

Parameters:

Name Type Description Default
dates Union[DatetimeIndex, List[datetime], datetime, str]

Dates to retrieve.

required
product str

MERRA-2 product short name.

'inst1_2d_asm_Nx'

Returns:

Type Description
List[str]

List of OPeNDAP URLs.

Source code in monetio/readers/merra2.py
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
def build_urls(
    self,
    dates: pd.DatetimeIndex | list[datetime.datetime] | datetime.datetime | str,
    product: str = "inst1_2d_asm_Nx",
) -> list[str]:
    """
    Build OPeNDAP URLs for MERRA-2 data based on dates and product.

    Parameters
    ----------
    dates : Union[pd.DatetimeIndex, List[datetime], datetime, str]
        Dates to retrieve.
    product : str, optional
        MERRA-2 product short name.

    Returns
    -------
    List[str]
        List of OPeNDAP URLs.
    """
    if isinstance(dates, str | datetime.datetime | pd.Timestamp):
        dates = pd.DatetimeIndex([pd.to_datetime(dates)])
    else:
        dates = pd.to_datetime(dates)

    # Product mapping to GES DISC OPeNDAP paths
    # Format: (ShortName.Version, CollectionName, ServerNumber)
    prod_map = {
        "inst1_2d_asm_Nx": ("M2I1NXASM.5.12.4", "inst1_2d_asm_Nx", "4"),
        "inst1_2d_int_Nx": ("M2I1NXINT.5.12.4", "inst1_2d_int_Nx", "4"),
        "inst1_2d_lfo_Nx": ("M2I1NXLFO.5.12.4", "inst1_2d_lfo_Nx", "4"),
        "inst3_2d_gas_Nx": ("M2I3NXGAS.5.12.4", "inst3_2d_gas_Nx", "4"),
        "statD_2d_slv_Nx": ("M2SDNXSLV.5.12.4", "statD_2d_slv_Nx", "4"),
        "tavg1_2d_adg_Nx": ("M2T1NXADG.5.12.4", "tavg1_2d_adg_Nx", "4"),
        "tavg1_2d_aer_Nx": ("M2T1NXAER.5.12.4", "tavg1_2d_aer_Nx", "4"),
        "tavg1_2d_chm_Nx": ("M2T1NXCHM.5.12.4", "tavg1_2d_chm_Nx", "4"),
        "tavg1_2d_csp_Nx": ("M2T1NXCSP.5.12.4", "tavg1_2d_csp_Nx", "4"),
        "tavg1_2d_flx_Nx": ("M2T1NXFLX.5.12.4", "tavg1_2d_flx_Nx", "4"),
        "tavg1_2d_int_Nx": ("M2T1NXINT.5.12.4", "tavg1_2d_int_Nx", "4"),
        "tavg1_2d_lfo_Nx": ("M2T1NXLFO.5.12.4", "tavg1_2d_lfo_Nx", "4"),
        "tavg1_2d_lnd_Nx": ("M2T1NXLND.5.12.4", "tavg1_2d_lnd_Nx", "4"),
        "tavg1_2d_ocn_Nx": ("M2T1NXOCN.5.12.4", "tavg1_2d_ocn_Nx", "4"),
        "tavg1_2d_rad_Nx": ("M2T1NXRAD.5.12.4", "tavg1_2d_rad_Nx", "4"),
        "tavg1_2d_slv_Nx": ("M2T1NXSLV.5.12.4", "tavg1_2d_slv_Nx", "4"),
        "tavg3_2d_glc_Nx": ("M2T3NXGLC.5.12.4", "tavg3_2d_glc_Nx", "4"),
        "inst3_3d_asm_Np": ("M2I3NPASM.5.12.4", "inst3_3d_asm_Np", "5"),
        "inst3_3d_aer_Nv": ("M2I3NVAER.5.12.4", "inst3_3d_aer_Nv", "5"),
        "inst3_3d_asm_Nv": ("M2I3NVASM.5.12.4", "inst3_3d_asm_Nv", "5"),
        "inst3_3d_chm_Nv": ("M2I3NVCHM.5.12.4", "inst3_3d_chm_Nv", "5"),
        "inst3_3d_gas_Nv": ("M2I3NVGAS.5.12.4", "inst3_3d_gas_Nv", "5"),
        "inst6_3d_ana_Np": ("M2I6NPANA.5.12.4", "inst6_3d_ana_Np", "5"),
        "inst6_3d_ana_Nv": ("M2I6NVANA.5.12.4", "inst6_3d_ana_Nv", "5"),
        "tavg3_3d_mst_Ne": ("M2T3NEMST.5.12.4", "tavg3_3d_mst_Ne", "5"),
        "tavg3_3d_nav_Ne": ("M2T3NENAV.5.12.4", "tavg3_3d_nav_Ne", "5"),
        "tavg3_3d_trb_Ne": ("M2T3NETRB.5.12.4", "tavg3_3d_trb_Ne", "5"),
        "tavg3_3d_cld_Np": ("M2T3NPCLD.5.12.4", "tavg3_3d_cld_Np", "5"),
        "tavg3_3d_mst_Np": ("M2T3NPMST.5.12.4", "tavg3_3d_mst_Np", "5"),
        "tavg3_3d_odt_Np": ("M2T3NPODT.5.12.4", "tavg3_3d_odt_Np", "5"),
        "tavg3_3d_qdt_Np": ("M2T3NPQDT.5.12.4", "tavg3_3d_qdt_Np", "5"),
        "tavg3_3d_rad_Np": ("M2T3NPRAD.5.12.4", "tavg3_3d_rad_Np", "5"),
        "tavg3_3d_tdt_Np": ("M2T3NPTDT.5.12.4", "tavg3_3d_tdt_Np", "5"),
        "tavg3_3d_trb_Np": ("M2T3NPTRB.5.12.4", "tavg3_3d_trb_Np", "5"),
        "tavg3_3d_udt_Np": ("M2T3NPUDT.5.12.4", "tavg3_3d_udt_Np", "5"),
        "tavg3_3d_asm_Nv": ("M2T3NVASM.5.12.4", "tavg3_3d_asm_Nv", "5"),
        "tavg3_3d_cld_Nv": ("M2T3NVCLD.5.12.4", "tavg3_3d_cld_Nv", "5"),
        "tavg3_3d_mst_Nv": ("M2T3NVMST.5.12.4", "tavg3_3d_mst_Nv", "5"),
        "tavg3_3d_rad_Nv": ("M2T3NVRAD.5.12.4", "tavg3_3d_rad_Nv", "5"),
        "inst3_3d_chm_Np": ("M2I3NPCHM.5.12.4", "inst3_3d_chm_Np", "5"),
    }

    if product not in prod_map:
        raise ValueError(f"Unknown product: {product}. Available: {list(prod_map.keys())}")

    short_name, coll_name, server = prod_map[product]
    base_url = f"https://goldsmr{server}.gesdisc.eosdis.nasa.gov/opendap/MERRA2/{short_name}"

    urls = []
    for d in dates.floor("D").unique():
        # MERRA-2 filenames usually include the date and the product name
        # Example: MERRA2_400.inst1_2d_asm_Nx.20240101.nc4
        # The stream ID (400, 300, 200, 100) depends on the year.
        year = d.year
        if 1980 <= year <= 1991:
            stream = "100"
        elif 1992 <= year <= 2000:
            stream = "200"
        elif 2001 <= year <= 2010:
            stream = "300"
        else:
            stream = "400"

        date_str = d.strftime("%Y%m%d")
        url = f"{base_url}/{d.strftime('%Y/%m')}/MERRA2_{stream}.{coll_name}.{date_str}.nc4"
        urls.append(url)

    return urls

open_dataset(files=None, dates=None, product='inst1_2d_asm_Nx', username=None, password=None, virtualizarr=None, **kwargs)

Reads MERRA-2 data.

Parameters:

Name Type Description Default
files Union[str, List[str]]

File path(s) or URL(s). If None, will try to build URLs using dates and product.

None
dates Union[DatetimeIndex, List[datetime], datetime, str]

Dates to retrieve. Used if files is None.

None
product str

MERRA-2 product short name, by default "inst1_2d_asm_Nx". Common products: - 'inst1_2d_asm_Nx': Instantaneous 2D atmospheric fields - 'tavg1_2d_slv_Nx': Time-averaged 2D surface fields - 'inst3_3d_asm_Np': Instantaneous 3D atmospheric fields (pressure levels) - 'inst3_3d_chm_Np': Instantaneous 3D chemical fields (pressure levels)

'inst1_2d_asm_Nx'
username str

NASA Earthdata username. If provided, will setup .netrc.

None
password str

NASA Earthdata password. If provided, will setup .netrc.

None
**kwargs dict

Additional arguments passed to XarrayDriver.open.

{}

Returns:

Type Description
Dataset

The MERRA-2 dataset.

Source code in monetio/readers/merra2.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def open_dataset(
    self,
    files: str | list[str] | None = None,
    dates: pd.DatetimeIndex | list[datetime.datetime] | datetime.datetime | str | None = None,
    product: str = "inst1_2d_asm_Nx",
    username: str | None = None,
    password: str | None = None,
    virtualizarr: str | None = None,
    **kwargs,
) -> xr.Dataset:
    """
    Reads MERRA-2 data.

    Parameters
    ----------
    files : Union[str, List[str]], optional
        File path(s) or URL(s). If None, will try to build URLs using dates and product.
    dates : Union[pd.DatetimeIndex, List[datetime], datetime, str], optional
        Dates to retrieve. Used if files is None.
    product : str, optional
        MERRA-2 product short name, by default "inst1_2d_asm_Nx".
        Common products:
        - 'inst1_2d_asm_Nx': Instantaneous 2D atmospheric fields
        - 'tavg1_2d_slv_Nx': Time-averaged 2D surface fields
        - 'inst3_3d_asm_Np': Instantaneous 3D atmospheric fields (pressure levels)
        - 'inst3_3d_chm_Np': Instantaneous 3D chemical fields (pressure levels)
    username : str, optional
        NASA Earthdata username. If provided, will setup .netrc.
    password : str, optional
        NASA Earthdata password. If provided, will setup .netrc.
    **kwargs : dict
        Additional arguments passed to XarrayDriver.open.

    Returns
    -------
    xr.Dataset
        The MERRA-2 dataset.
    """
    if username and password:
        setup_netrc(username, password)

    if files is None:
        if dates is None:
            raise ValueError("Either 'files' or 'dates' must be provided.")
        files = self.build_urls(dates, product=product)

    if "preprocess" not in kwargs:
        from functools import partial

        kwargs["preprocess"] = partial(merra2_preprocess, product=product)

    if virtualizarr is not None:
        kwargs["virtualizarr_file"] = virtualizarr

    ds = super().open_dataset(files, **kwargs)

    # Update history
    ds = update_history(ds, f"Read MERRA-2 {product} data.")

    return ds

merra2_preprocess(ds, product=None)

Preprocess MERRA-2 dataset: standardize coordinates and metadata.

Parameters:

Name Type Description Default
ds Dataset

Input dataset.

required
product str

MERRA-2 product short name.

None

Returns:

Type Description
Dataset

Processed dataset.

Source code in monetio/readers/merra2.py
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
def merra2_preprocess(ds: xr.Dataset, product: str | None = None) -> xr.Dataset:
    """
    Preprocess MERRA-2 dataset: standardize coordinates and metadata.

    Parameters
    ----------
    ds : xr.Dataset
        Input dataset.
    product : str, optional
        MERRA-2 product short name.

    Returns
    -------
    xr.Dataset
        Processed dataset.
    """
    # 1. Pre-standardize coordinates to avoid losing them during dimension rename
    # if they share the same name.
    if "lat" in ds.coords and "latitude" not in ds.coords:
        ds = ds.assign_coords(latitude=ds.lat)
    if "lon" in ds.coords and "longitude" not in ds.coords:
        ds = ds.assign_coords(longitude=ds.lon)

    # 2. Standardize dimensions and coordinates
    # MERRA-2 typically uses 'lat', 'lon', 'time', 'lev'.
    ds = standardize_satellite_coords(
        ds,
        lat_name="latitude",
        lon_name="longitude",
        y_dim=["lat", "nlat", "y"],
        x_dim=["lon", "nlon", "x"],
        z_dim=["lev", "level", "layer"],
    )

    # 3. Expand 1D coords to 2D for UGRID/CF compliance in MONETIO if needed
    if "latitude" in ds.coords and ds["latitude"].ndim == 1:
        if "longitude" in ds.coords and ds["longitude"].ndim == 1:
            # Use lazy broadcasting
            lons, lats = xr.broadcast(ds.longitude, ds.latitude)
            # Ensure (y, x) order which is standard for gridded data in MONETIO
            if "y" in lons.dims and "x" in lons.dims:
                lons = lons.transpose("y", "x")
                lats = lats.transpose("y", "x")
            # Re-assign as 2D coordinates
            ds = ds.assign_coords(longitude=lons, latitude=lats)

    # 3. Variable renaming to standard names if they exist
    mapping = {
        "PS": "surface_pressure",
        "T": "temperature",
        "QV": "specific_humidity",
        "U": "u_wind",
        "V": "v_wind",
    }
    rename_dict = {
        old: new for old, new in mapping.items() if old in ds.variables and new not in ds.variables
    }
    if rename_dict:
        ds = ds.rename(rename_dict)

    # 4. Calculate Pressure (Lazy)
    ds = _add_merra2_pressure(ds)

    # 5. Scientific Hygiene
    ds = _scientific_hygiene(ds)

    # Update history
    ds = update_history(ds, "Preprocessed MERRA-2 data using standardized preprocessing.")

    return ds