meteostat 1.7.6__py3-none-any.whl → 2.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. meteostat/__init__.py +38 -19
  2. meteostat/api/config.py +158 -0
  3. meteostat/api/daily.py +76 -0
  4. meteostat/api/hourly.py +80 -0
  5. meteostat/api/interpolate.py +378 -0
  6. meteostat/api/inventory.py +59 -0
  7. meteostat/api/merge.py +103 -0
  8. meteostat/api/monthly.py +73 -0
  9. meteostat/api/normals.py +144 -0
  10. meteostat/api/point.py +30 -0
  11. meteostat/api/stations.py +234 -0
  12. meteostat/api/timeseries.py +334 -0
  13. meteostat/core/cache.py +212 -59
  14. meteostat/core/data.py +203 -0
  15. meteostat/core/logger.py +9 -0
  16. meteostat/core/network.py +82 -0
  17. meteostat/core/parameters.py +112 -0
  18. meteostat/core/providers.py +184 -0
  19. meteostat/core/schema.py +170 -0
  20. meteostat/core/validator.py +38 -0
  21. meteostat/enumerations.py +149 -0
  22. meteostat/interpolation/idw.py +120 -0
  23. meteostat/interpolation/lapserate.py +91 -0
  24. meteostat/interpolation/nearest.py +31 -0
  25. meteostat/parameters.py +354 -0
  26. meteostat/providers/dwd/climat.py +166 -0
  27. meteostat/providers/dwd/daily.py +144 -0
  28. meteostat/providers/dwd/hourly.py +218 -0
  29. meteostat/providers/dwd/monthly.py +138 -0
  30. meteostat/providers/dwd/mosmix.py +351 -0
  31. meteostat/providers/dwd/poi.py +117 -0
  32. meteostat/providers/dwd/shared.py +155 -0
  33. meteostat/providers/eccc/daily.py +87 -0
  34. meteostat/providers/eccc/hourly.py +104 -0
  35. meteostat/providers/eccc/monthly.py +66 -0
  36. meteostat/providers/eccc/shared.py +45 -0
  37. meteostat/providers/index.py +496 -0
  38. meteostat/providers/meteostat/daily.py +65 -0
  39. meteostat/providers/meteostat/daily_derived.py +110 -0
  40. meteostat/providers/meteostat/hourly.py +66 -0
  41. meteostat/providers/meteostat/monthly.py +45 -0
  42. meteostat/providers/meteostat/monthly_derived.py +106 -0
  43. meteostat/providers/meteostat/shared.py +93 -0
  44. meteostat/providers/metno/forecast.py +186 -0
  45. meteostat/providers/noaa/ghcnd.py +228 -0
  46. meteostat/providers/noaa/isd_lite.py +142 -0
  47. meteostat/providers/noaa/metar.py +163 -0
  48. meteostat/typing.py +113 -0
  49. meteostat/utils/conversions.py +231 -0
  50. meteostat/utils/data.py +194 -0
  51. meteostat/utils/geo.py +28 -0
  52. meteostat/utils/guards.py +51 -0
  53. meteostat/utils/parsers.py +161 -0
  54. meteostat/utils/types.py +113 -0
  55. meteostat/utils/validators.py +31 -0
  56. meteostat-2.0.1.dist-info/METADATA +130 -0
  57. meteostat-2.0.1.dist-info/RECORD +64 -0
  58. {meteostat-1.7.6.dist-info → meteostat-2.0.1.dist-info}/WHEEL +1 -2
  59. meteostat/core/loader.py +0 -103
  60. meteostat/core/warn.py +0 -34
  61. meteostat/enumerations/granularity.py +0 -22
  62. meteostat/interface/base.py +0 -39
  63. meteostat/interface/daily.py +0 -118
  64. meteostat/interface/hourly.py +0 -154
  65. meteostat/interface/meteodata.py +0 -210
  66. meteostat/interface/monthly.py +0 -109
  67. meteostat/interface/normals.py +0 -245
  68. meteostat/interface/point.py +0 -143
  69. meteostat/interface/stations.py +0 -252
  70. meteostat/interface/timeseries.py +0 -237
  71. meteostat/series/aggregate.py +0 -48
  72. meteostat/series/convert.py +0 -28
  73. meteostat/series/count.py +0 -17
  74. meteostat/series/coverage.py +0 -20
  75. meteostat/series/fetch.py +0 -28
  76. meteostat/series/interpolate.py +0 -47
  77. meteostat/series/normalize.py +0 -76
  78. meteostat/series/stations.py +0 -22
  79. meteostat/units.py +0 -149
  80. meteostat/utilities/__init__.py +0 -0
  81. meteostat/utilities/aggregations.py +0 -37
  82. meteostat/utilities/endpoint.py +0 -33
  83. meteostat/utilities/helpers.py +0 -70
  84. meteostat/utilities/mutations.py +0 -89
  85. meteostat/utilities/validations.py +0 -30
  86. meteostat-1.7.6.dist-info/METADATA +0 -112
  87. meteostat-1.7.6.dist-info/RECORD +0 -39
  88. meteostat-1.7.6.dist-info/top_level.txt +0 -1
  89. /meteostat/{core → api}/__init__.py +0 -0
  90. /meteostat/{enumerations → interpolation}/__init__.py +0 -0
  91. /meteostat/{interface → providers}/__init__.py +0 -0
  92. /meteostat/{interface/interpolate.py → py.typed} +0 -0
  93. /meteostat/{series → utils}/__init__.py +0 -0
  94. {meteostat-1.7.6.dist-info → meteostat-2.0.1.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,378 @@
1
+ """
2
+ Interpolation Module
3
+
4
+ Provides spatial interpolation functions for meteorological data.
5
+ """
6
+
7
+ from typing import Optional, Union
8
+
9
+ import numpy as np
10
+ import pandas as pd
11
+
12
+ from meteostat.api.point import Point
13
+ from meteostat.api.timeseries import TimeSeries
14
+ from meteostat.typing import Station
15
+ from meteostat.enumerations import Parameter
16
+ from meteostat.interpolation.lapserate import apply_lapse_rate
17
+ from meteostat.interpolation.nearest import nearest_neighbor
18
+ from meteostat.interpolation.idw import inverse_distance_weighting
19
+ from meteostat.utils.data import aggregate_sources, reshape_by_source, stations_to_df
20
+ from meteostat.utils.geo import get_distance
21
+ from meteostat.utils.parsers import parse_station
22
+ from meteostat.core.schema import schema_service
23
+ from meteostat.core.logger import logger
24
+
25
+
26
+ # Parameters that are categorical and should not use IDW interpolation
27
+ CATEGORICAL_PARAMETERS = {Parameter.WDIR, Parameter.CLDC, Parameter.COCO}
28
+
29
+
30
+ def _create_timeseries(
31
+ ts: TimeSeries, point: Point, df: Optional[pd.DataFrame] = None
32
+ ) -> TimeSeries:
33
+ """
34
+ Create a TimeSeries object from interpolated DataFrame
35
+ """
36
+ parsed = parse_station(point)
37
+ stations_list = [parsed] if isinstance(parsed, Station) else parsed
38
+
39
+ # Convert stations to DataFrame
40
+ stations_df = stations_to_df(stations_list)
41
+
42
+ return TimeSeries(
43
+ ts.granularity,
44
+ stations_df,
45
+ df=df,
46
+ start=ts.start,
47
+ end=ts.end,
48
+ timezone=ts.timezone,
49
+ )
50
+
51
+
52
+ def _add_source_columns(
53
+ result: pd.DataFrame,
54
+ df: pd.DataFrame,
55
+ ) -> pd.DataFrame:
56
+ """
57
+ Add source columns to the result DataFrame
58
+ """
59
+ source_cols = [c for c in df.columns if c.endswith("_source")]
60
+ if source_cols:
61
+ grouped = df.groupby("time")[source_cols].agg(aggregate_sources)
62
+ if isinstance(grouped, pd.Series):
63
+ grouped = grouped.to_frame(name=source_cols[0])
64
+ grouped.index.name = "time"
65
+
66
+ # Safely align on time and add/fill source columns without causing overlaps
67
+ result_has_time_col = "time" in result.columns
68
+ if result_has_time_col:
69
+ result = result.set_index("time")
70
+
71
+ # Ensure both frames align on the same index (time)
72
+ # For each source column, add it if missing or fill NaNs if present
73
+ for col in source_cols:
74
+ if col in grouped.columns:
75
+ if col in result.columns:
76
+ # Fill missing values in result using aggregated sources
77
+ result[col] = result[col].where(result[col].notna(), grouped[col])
78
+ else:
79
+ # Add aggregated source column
80
+ result[col] = grouped[col]
81
+
82
+ if result_has_time_col:
83
+ result = result.reset_index()
84
+ return result
85
+
86
+
87
+ def _prepare_data_with_distances(
88
+ df: pd.DataFrame, point: Point, elevation_weight: float
89
+ ) -> pd.DataFrame:
90
+ """
91
+ Add distance and elevation calculations to the DataFrame
92
+ """
93
+ # Add distance column
94
+ df["distance"] = get_distance(
95
+ point.latitude, point.longitude, df["latitude"], df["longitude"]
96
+ )
97
+
98
+ # Add effective distance column if elevation is available
99
+ if point.elevation is not None and "elevation" in df.columns:
100
+ elev_diff = np.abs(df["elevation"] - point.elevation)
101
+ df["effective_distance"] = np.sqrt(
102
+ df["distance"] ** 2 + (elev_diff * elevation_weight) ** 2
103
+ )
104
+ else:
105
+ df["effective_distance"] = df["distance"]
106
+
107
+ # Add elevation difference column
108
+ if "elevation" in df.columns and point.elevation is not None:
109
+ df["elevation_diff"] = np.abs(df["elevation"] - point.elevation)
110
+ else:
111
+ df["elevation_diff"] = np.nan
112
+
113
+ return df
114
+
115
+
116
+ def _should_use_nearest_neighbor(
117
+ df: pd.DataFrame,
118
+ point: Point,
119
+ distance_threshold: Union[int, None],
120
+ elevation_threshold: Union[int, None],
121
+ ) -> bool:
122
+ """
123
+ Determine if nearest neighbor should be used based on thresholds
124
+ """
125
+ min_distance = df["distance"].min()
126
+ use_nearest = distance_threshold is None or min_distance <= distance_threshold
127
+
128
+ if use_nearest and point.elevation is not None and "elevation" in df.columns:
129
+ min_elev_diff = np.abs(df["elevation"] - point.elevation).min()
130
+ use_nearest = (
131
+ elevation_threshold is None or min_elev_diff <= elevation_threshold
132
+ )
133
+
134
+ return use_nearest
135
+
136
+
137
+ def _get_categorical_columns(df: pd.DataFrame) -> list:
138
+ """
139
+ Identify categorical columns in the data (excluding source columns)
140
+ """
141
+ data_cols = [c for c in df.columns if not c.endswith("_source")]
142
+ return [c for c in data_cols if c in CATEGORICAL_PARAMETERS]
143
+
144
+
145
+ def _interpolate_with_nearest_neighbor(
146
+ df: pd.DataFrame,
147
+ ts: TimeSeries,
148
+ point: Point,
149
+ distance_threshold: Union[int, None],
150
+ elevation_threshold: Union[int, None],
151
+ ) -> Optional[pd.DataFrame]:
152
+ """
153
+ Perform nearest neighbor interpolation with threshold filtering
154
+ """
155
+ distance_filter = (
156
+ pd.Series([True] * len(df), index=df.index)
157
+ if distance_threshold is None
158
+ else (df["distance"] <= distance_threshold)
159
+ )
160
+ elevation_filter = (
161
+ pd.Series([True] * len(df), index=df.index)
162
+ if elevation_threshold is None
163
+ else (np.abs(df["elevation"] - point.elevation) <= elevation_threshold)
164
+ )
165
+ df_filtered = df[distance_filter & elevation_filter]
166
+ return nearest_neighbor(df_filtered, ts, point)
167
+
168
+
169
+ def _interpolate_with_idw_and_categorical(
170
+ df: pd.DataFrame,
171
+ ts: TimeSeries,
172
+ point: Point,
173
+ categorical_cols: list,
174
+ power: float,
175
+ ) -> Optional[pd.DataFrame]:
176
+ """
177
+ Perform IDW interpolation for non-categorical parameters and nearest neighbor for categorical
178
+ """
179
+ # For categorical parameters, always use nearest neighbor
180
+ if categorical_cols:
181
+ df_categorical = nearest_neighbor(df, ts, point)
182
+ # Keep only categorical columns that exist in the result
183
+ existing_categorical = [
184
+ c for c in categorical_cols if c in df_categorical.columns
185
+ ]
186
+ df_categorical = (
187
+ df_categorical[existing_categorical]
188
+ if existing_categorical
189
+ else pd.DataFrame()
190
+ )
191
+ else:
192
+ df_categorical = pd.DataFrame()
193
+
194
+ # Perform IDW interpolation for all parameters
195
+ idw_func = inverse_distance_weighting(power=power)
196
+ df_idw = idw_func(df, ts, point)
197
+
198
+ # Remove categorical columns from IDW result if they exist
199
+ if not df_categorical.empty and df_idw is not None:
200
+ # Drop categorical columns from IDW result
201
+ idw_cols_to_keep = [c for c in df_idw.columns if c not in categorical_cols]
202
+ df_idw = df_idw[idw_cols_to_keep] if idw_cols_to_keep else pd.DataFrame()
203
+
204
+ # Combine categorical (nearest) and non-categorical (IDW) results
205
+ if not df_categorical.empty and not df_idw.empty:
206
+ return pd.concat([df_idw, df_categorical], axis=1)
207
+ elif not df_categorical.empty:
208
+ return df_categorical
209
+ else:
210
+ return df_idw
211
+
212
+
213
+ def _merge_interpolation_results(
214
+ df_nearest: Optional[pd.DataFrame],
215
+ df_idw: Optional[pd.DataFrame],
216
+ use_nearest: bool,
217
+ ) -> Optional[pd.DataFrame]:
218
+ """
219
+ Merge nearest neighbor and IDW results with appropriate priority
220
+ """
221
+ if use_nearest and df_nearest is not None and len(df_nearest) > 0:
222
+ if df_idw is not None:
223
+ # Combine nearest and IDW results, prioritizing nearest values
224
+ return df_nearest.combine_first(df_idw)
225
+ else:
226
+ return df_nearest
227
+ else:
228
+ return df_idw
229
+
230
+
231
+ def _postprocess_result(
232
+ result: pd.DataFrame, df: pd.DataFrame, ts: TimeSeries
233
+ ) -> pd.DataFrame:
234
+ """
235
+ Post-process the interpolation result: drop location columns, add sources, format, reshape
236
+ """
237
+ # Drop location-related columns
238
+ result = result.drop(
239
+ [
240
+ "latitude",
241
+ "longitude",
242
+ "elevation",
243
+ "distance",
244
+ "effective_distance",
245
+ "elevation_diff",
246
+ ],
247
+ axis=1,
248
+ errors="ignore",
249
+ )
250
+
251
+ # Add source columns
252
+ result = _add_source_columns(result, df)
253
+
254
+ # Reshape by source
255
+ result = reshape_by_source(result)
256
+
257
+ # Add station index
258
+ result["station"] = "$0001"
259
+ result = result.set_index("station", append=True).reorder_levels(
260
+ ["station", "time", "source"]
261
+ )
262
+
263
+ # Reorder columns to match the canonical schema order
264
+ result = schema_service.purge(result, ts.parameters)
265
+
266
+ # Format the result using schema_service to apply proper rounding
267
+ result = schema_service.format(result, ts.granularity)
268
+
269
+ return result
270
+
271
+
272
+ def interpolate(
273
+ ts: TimeSeries,
274
+ point: Point,
275
+ distance_threshold: Union[int, None] = 5000,
276
+ elevation_threshold: Union[int, None] = 50,
277
+ elevation_weight: float = 10,
278
+ power: float = 2.0,
279
+ lapse_rate: Union[float, None] = 6.5,
280
+ lapse_rate_threshold: int = 50,
281
+ ) -> TimeSeries:
282
+ """
283
+ Interpolate time series data spatially to a specific point.
284
+
285
+ Parameters
286
+ ----------
287
+ ts : TimeSeries
288
+ The time series to interpolate.
289
+ point : Point
290
+ The point to interpolate the data for.
291
+ distance_threshold : int, optional
292
+ Maximum distance (in meters) to use nearest neighbor (default: 5000).
293
+ Beyond this, IDW is used.
294
+ elevation_threshold : int, optional
295
+ Maximum elevation difference (in meters) to use nearest neighbor (default: 50).
296
+ Beyond this, IDW is used even if distance is within threshold.
297
+ elevation_weight : float, optional
298
+ Weight for elevation difference in distance calculation (default: 0.1).
299
+ The effective distance is calculated as:
300
+ sqrt(horizontal_distance^2 + (elevation_diff * elevation_weight)^2)
301
+ power : float, optional
302
+ Power parameter for IDW (default: 2.0). Higher values give more
303
+ weight to closer stations.
304
+ lapse_rate : float, optional
305
+ Apply lapse rate correction based on elevation difference (default: 6.5).
306
+ lapse_rate_threshold : int, optional
307
+ Elevation difference threshold (in meters) to apply lapse rate correction
308
+ (default: 50). If the elevation difference between the point and stations
309
+ is less than this, no correction is applied.
310
+
311
+ Returns
312
+ -------
313
+ pd.DataFrame or None
314
+ A DataFrame containing the interpolated data for the specified point,
315
+ or None if no data is available.
316
+ """
317
+ # Fetch DataFrame, filling missing values and adding location data
318
+ df = ts.fetch(fill=True, location=True, sources=True)
319
+
320
+ # If no data is returned, return None
321
+ if df is None:
322
+ logger.debug("No data available for interpolation. Returning empty TimeSeries.")
323
+ return _create_timeseries(ts, point)
324
+
325
+ # Prepare data with distance and elevation calculations
326
+ df = _prepare_data_with_distances(df, point, elevation_weight)
327
+
328
+ # Apply lapse rate if specified and elevation is available
329
+ if (
330
+ lapse_rate
331
+ and point.elevation
332
+ and df["elevation_diff"].max() >= lapse_rate_threshold
333
+ ):
334
+ logger.debug("Applying lapse rate correction.")
335
+ df = apply_lapse_rate(df, point.elevation, lapse_rate)
336
+
337
+ # Determine if nearest neighbor should be used
338
+ use_nearest = _should_use_nearest_neighbor(
339
+ df, point, distance_threshold, elevation_threshold
340
+ )
341
+
342
+ # Identify categorical columns
343
+ categorical_cols = _get_categorical_columns(df)
344
+ logger.debug(f"Categorical columns identified: {categorical_cols}")
345
+
346
+ # Perform interpolation
347
+ df_nearest = None
348
+ df_idw = None
349
+
350
+ if use_nearest:
351
+ logger.debug("Using nearest neighbor interpolation.")
352
+ df_nearest = _interpolate_with_nearest_neighbor(
353
+ df, ts, point, distance_threshold, elevation_threshold
354
+ )
355
+
356
+ # Use IDW if nearest neighbor doesn't provide complete data
357
+ if (
358
+ not use_nearest
359
+ or df_nearest is None
360
+ or len(df_nearest) == 0
361
+ or df_nearest.isna().any().any()
362
+ ):
363
+ logger.debug("Using IDW interpolation.")
364
+ df_idw = _interpolate_with_idw_and_categorical(
365
+ df, ts, point, categorical_cols, power
366
+ )
367
+
368
+ # Merge results
369
+ result = _merge_interpolation_results(df_nearest, df_idw, use_nearest)
370
+
371
+ # If no data is returned, return None
372
+ if result is None or result.empty:
373
+ return _create_timeseries(ts, point)
374
+
375
+ # Post-process result
376
+ result = _postprocess_result(result, df, ts)
377
+
378
+ return _create_timeseries(ts, point, result)
@@ -0,0 +1,59 @@
1
+ """
2
+ Inventory Module
3
+
4
+ Provides classes for working with weather station data inventories.
5
+ """
6
+
7
+ from datetime import date, datetime
8
+ from typing import List, Optional
9
+
10
+ import pandas as pd
11
+
12
+ from meteostat.enumerations import Parameter
13
+
14
+
15
+ class Inventory:
16
+ """
17
+ A weather station's data inventory
18
+ """
19
+
20
+ df: Optional[pd.DataFrame] = None
21
+
22
+ def __init__(self, df: Optional[pd.DataFrame] = None):
23
+ if df is not None and not df.empty:
24
+ self.df = df
25
+
26
+ @property
27
+ def start(self) -> Optional[date]:
28
+ """
29
+ Get the earliest start date from the inventory
30
+ """
31
+ return (
32
+ datetime.strptime(self.df["start"].min(), "%Y-%m-%d").date()
33
+ if self.df is not None
34
+ else None
35
+ )
36
+
37
+ @property
38
+ def end(self) -> Optional[date]:
39
+ """
40
+ Get the latest end date from the inventory
41
+ """
42
+ return (
43
+ datetime.strptime(self.df["end"].max(), "%Y-%m-%d").date()
44
+ if self.df is not None
45
+ else None
46
+ )
47
+
48
+ @property
49
+ def parameters(self) -> Optional[List[Parameter]]:
50
+ """
51
+ Get the list of available parameters from the inventory
52
+ """
53
+ if self.df is None:
54
+ return []
55
+
56
+ return [
57
+ Parameter[parameter.upper()]
58
+ for parameter in self.df.index.get_level_values("parameter").unique()
59
+ ]
meteostat/api/merge.py ADDED
@@ -0,0 +1,103 @@
1
+ """
2
+ Concatenation Module
3
+
4
+ Provides functions to concatenate multiple time series objects into one.
5
+ """
6
+
7
+ from copy import copy
8
+ from datetime import datetime
9
+ from typing import List, Optional
10
+
11
+ import pandas as pd
12
+
13
+ from meteostat.core.data import data_service
14
+ from meteostat.core.schema import schema_service
15
+ from meteostat.api.timeseries import TimeSeries
16
+
17
+
18
+ def _get_dt(
19
+ dt_a: Optional[datetime], dt_b: Optional[datetime], start=True
20
+ ) -> Optional[datetime]:
21
+ """
22
+ Return the earlier or later (depending on "start" argument) of two datetimes,
23
+ considering None as 'no value'.
24
+
25
+ If both are None, return None.
26
+ """
27
+ if dt_a is None:
28
+ return dt_b
29
+ if dt_b is None:
30
+ return dt_a
31
+ return min(dt_a, dt_b) if start else max(dt_a, dt_b)
32
+
33
+
34
+ def merge(objs: List[TimeSeries]) -> TimeSeries:
35
+ """
36
+ Merge one or multiple Meteostat time series into a common one
37
+
38
+ In case of duplicate index, the last row will be prefered.
39
+ Hence, please pass newest data last.
40
+
41
+ Parameters
42
+ ----------
43
+ objs : List[TimeSeries]
44
+ List of time series objects to concatenate
45
+
46
+ Returns
47
+ -------
48
+ TimeSeries
49
+ Concatenated time series object
50
+
51
+ Raises
52
+ ------
53
+ ValueError
54
+ If the time series objects have divergent granularity or time zone
55
+ """
56
+ ts = objs[0]
57
+
58
+ if not all(
59
+ obj.granularity == ts.granularity and obj.timezone == ts.timezone
60
+ for obj in objs[1:]
61
+ ):
62
+ raise ValueError(
63
+ "Can't concatenate time series objects with divergent granularity or time zone"
64
+ )
65
+
66
+ stations = copy(ts.stations)
67
+ start = copy(ts.start)
68
+ end = copy(ts.end)
69
+ parameters = ts.parameters
70
+ multi_station = ts._multi_station
71
+
72
+ for obj in objs[1:]:
73
+ stations = (
74
+ pd.concat([stations, obj.stations])
75
+ .reset_index()
76
+ .drop_duplicates(subset=["id"])
77
+ .set_index("id")
78
+ )
79
+ start = _get_dt(start, obj.start)
80
+ end = _get_dt(end, obj.end, False)
81
+ parameters.extend(obj.parameters)
82
+ if (
83
+ obj._multi_station
84
+ or stations.index.get_level_values("id")[0]
85
+ != obj.stations.index.get_level_values("id")[0]
86
+ ):
87
+ multi_station = True
88
+
89
+ df = data_service.concat_fragments(
90
+ [obj._df for obj in objs if obj._df is not None],
91
+ list(dict.fromkeys(parameters)),
92
+ )
93
+ df = schema_service.format(df, ts.granularity)
94
+
95
+ return TimeSeries(
96
+ ts.granularity,
97
+ stations,
98
+ df,
99
+ start,
100
+ end,
101
+ ts.timezone,
102
+ multi_station=multi_station,
103
+ )
@@ -0,0 +1,73 @@
1
+ """
2
+ Monthly Time Series Data
3
+
4
+ Access monthly time series data for one or multiple weather stations.
5
+ """
6
+
7
+ from typing import List, Optional
8
+ from datetime import datetime, date
9
+
10
+ import pandas as pd
11
+
12
+ from meteostat.core.data import data_service
13
+ from meteostat.enumerations import Parameter, Provider, Granularity
14
+ from meteostat.typing import Station, Request
15
+ from meteostat.api.point import Point
16
+ from meteostat.utils.parsers import parse_station, parse_time
17
+
18
+ DEFAULT_PARAMETERS = [
19
+ Parameter.TEMP,
20
+ Parameter.TMIN,
21
+ Parameter.TMAX,
22
+ Parameter.TXMN,
23
+ Parameter.TXMX,
24
+ Parameter.PRCP,
25
+ Parameter.PRES,
26
+ Parameter.TSUN,
27
+ ]
28
+
29
+
30
+ def monthly(
31
+ station: str | Station | Point | List[str | Station | Point] | pd.DataFrame,
32
+ start: Optional[datetime | date],
33
+ end: Optional[datetime | date],
34
+ parameters: Optional[List[Parameter]] = None,
35
+ providers: Optional[List[Provider]] = None,
36
+ ):
37
+ """
38
+ Access monthly time series data.
39
+
40
+ Parameters
41
+ ----------
42
+ station : str, Station, Point, List[str | Station | Point], pd.Index, pd.Series
43
+ Weather station(s) or Point(s) to query data for. Can be a single station/point or a list.
44
+ Points are converted to virtual stations with IDs like $0001, $0002, etc.
45
+ start : datetime, date, optional
46
+ Start date for the data query. If None, the earliest available date will be used.
47
+ end : datetime, date, optional
48
+ End date for the data query. If None, the latest available date will be used.
49
+ parameters : List[Parameter], optional
50
+ List of parameters to include in the data query. Defaults to a set of common parameters.
51
+ providers : List[Provider], optional
52
+ List of data providers to use for the query. Defaults to the monthly provider.
53
+
54
+ Returns
55
+ -------
56
+ TimeSeries
57
+ A TimeSeries object containing the monthly data for the specified stations and parameters.
58
+ """
59
+ if parameters is None:
60
+ parameters = DEFAULT_PARAMETERS
61
+ if providers is None:
62
+ providers = [Provider.MONTHLY]
63
+
64
+ req = Request(
65
+ granularity=Granularity.MONTHLY,
66
+ providers=providers,
67
+ parameters=parameters,
68
+ station=parse_station(station),
69
+ start=parse_time(start),
70
+ end=parse_time(end, is_end=True),
71
+ )
72
+
73
+ return data_service.fetch(req)