1"""Functions accessing the metadata of SERIES, SINGLETSFRAMEs and MULTITSFRAMEs
2
3The functions are thin wrappers over the methods of the models of the metadata conventions,
4see read_metadata. The methods resolve the fallbacks of the conventions, e.g. a missing display
5name falls back to the name, see hdhelpers.metadata._reading.
6
7The metadata is the model attached to the object (see attach_metadata), whatever data type it
8belongs to, e.g. the metadata of a SINGLETSFRAME on a Series taken from it. Otherwise the
9metadata in attrs is parsed as the data type of the function. Metadata which does not follow
10the conventions gives no information: None or empty defaultdicts. For a SERIES or SINGLETSFRAME,
11the single metric is the one named by dataset_metadata.single_metric, otherwise the only one.
12
13Fields of the functions taking one can be dotted paths, e.g. "inherited.site".
14"""
15
16import datetime
17from collections import defaultdict
18from collections.abc import Callable
19from typing import Any
20
21import pandas as pd
22
23from hdhelpers.metadata._reading import (
24 DISPLAY_NAME_FIELDS,
25 SHORT_DISPLAY_NAME_FIELDS,
26 Recording,
27 dataset_default_timezone,
28 dataset_recording,
29)
30from hdhelpers.metadata.attached import META_KEY, AnyMetadata, infer_data_type, read_metadata
31from hdhelpers.metadata.conventions import TimeseriesDataType, _TimeseriesMetadataBase
32
33_DEFAULT_AGGREGATION_METHOD = "MEAN"
34
35
36def check_dataframe(data: pd.DataFrame) -> None:
37 if not isinstance(data, pd.DataFrame):
38 raise TypeError("Input is not a pd.DataFrame")
39
40
41def check_series(series: pd.Series) -> None:
42 if not isinstance(series, pd.Series):
43 raise TypeError("Input is not a pd.Series.")
44
45
46def check_series_or_dataframe(data: pd.Series | pd.DataFrame) -> None:
47 if not isinstance(data, pd.Series) and not isinstance(data, pd.DataFrame):
48 raise TypeError("Input is not a pd.Series or pd.DataFrame.")
49
50
51def _metadata(data: pd.Series | pd.DataFrame, data_type: TimeseriesDataType) -> AnyMetadata | None:
52 """The attached metadata of any data type, otherwise the attrs parsed as data_type"""
53 attached = data.attrs.get(META_KEY)
54 if isinstance(attached, _TimeseriesMetadataBase):
55 return attached # type: ignore[return-value]
56 return read_metadata(data, data_type)
57
58
59def _single_metric(meta: AnyMetadata | None) -> str | None:
60 """The single metric: dataset_metadata.single_metric if described, otherwise the only one"""
61 if meta is None:
62 return None
63 metric_ids = meta.metric_ids()
64 single_metric = meta.dataset_metadata.single_metric
65 if single_metric is not None:
66 return single_metric if single_metric in metric_ids else None
67 return metric_ids[0] if len(metric_ids) == 1 else None
68
69
70def _by_metric_by_column(
71 data: pd.DataFrame, fields: str | tuple[str, ...], include_inherited: bool
72) -> defaultdict[str, defaultdict[str, Any]]:
73 check_dataframe(data)
74 meta = _metadata(data, "MULTITSFRAME")
75 infos = (
76 {}
77 if meta is None
78 else meta.value_dimension_infos(fields, include_inherited=include_inherited)
79 )
80 return defaultdict(
81 lambda: defaultdict(lambda: None),
82 {metric: defaultdict(lambda: None, info) for metric, info in infos.items()},
83 )
84
85
86def _single_metric_by_column(
87 data: pd.DataFrame, fields: str | tuple[str, ...], include_inherited: bool
88) -> defaultdict[str, Any]:
89 check_dataframe(data)
90 meta = _metadata(data, "SINGLETSFRAME")
91 metric = _single_metric(meta)
92 if meta is None or metric is None:
93 return defaultdict(lambda: None)
94 return defaultdict(
95 lambda: None,
96 meta.value_dimension_infos(fields, include_inherited=include_inherited)[metric],
97 )
98
99
100def _series_value(series: pd.Series, fields: str | tuple[str, ...], include_inherited: bool) -> Any:
101 check_series(series)
102 meta = _metadata(series, "SERIES")
103 metric = _single_metric(meta)
104 if meta is None or metric is None:
105 return None
106 return meta.value_dimension_info(fields, metric, "value", include_inherited=include_inherited)
107
108
109def _single_metric_or_dataset(
110 data: pd.Series | pd.DataFrame,
111 data_type: TimeseriesDataType,
112 of_metric: Callable[[AnyMetadata, str], Any],
113 of_dataset: Callable[[AnyMetadata], Any],
114 empty: Any,
115) -> Any:
116 """Information of the single metric, of the dataset metadata if the metric is not found"""
117 meta = _metadata(data, data_type)
118 if meta is None:
119 return empty
120 metric = _single_metric(meta)
121 return of_dataset(meta) if metric is None else of_metric(meta, metric)
122
123
124# value dimensions of MULTITSFRAMEs
125
126
[docs]
127def get_units(
128 multitsframe: pd.DataFrame, *, include_inherited: bool = False
129) -> defaultdict[str, defaultdict[str, str | None]]:
130 """Read units for each metric and value column.
131
132 Args:
133 multitsframe: DataFrame with per-metric metadata in attrs.
134 include_inherited: Use inherited fields last, only for the "value" column.
135
136 Returns:
137 defaultdict[str, defaultdict[str, str | None]]:
138 Units as result[metric_id][column]; unavailable units are None.
139
140 Code example:
141
142 .. doctest:: metadata.get_units
143
144 >>> from hdhelpers.metadata.conventions import MULTITSFRAME_METADATA_EXAMPLE
145 >>> multitsframe.attrs = MULTITSFRAME_METADATA_EXAMPLE
146 >>> units = md.get_units(multitsframe)
147 >>> units["plant_a.temperature"]["value"]
148 '°C'
149 >>> units["unknown metric"]["value"] is None
150 True
151
152 See :ref:`metadata-getter-values` for shared examples and return shapes.
153 """ # noqa: E501
154 return _by_metric_by_column(multitsframe, "unit", include_inherited)
155
156
[docs]
157def get_names(
158 multitsframe: pd.DataFrame, *, include_inherited: bool = False
159) -> defaultdict[str, defaultdict[str, str | None]]:
160 """Read metadata names for each metric and value column.
161
162 For the "value" column, try the metric's name after the column's own name.
163
164 Args:
165 multitsframe: DataFrame with per-metric metadata in attrs.
166 include_inherited: Use inherited fields last, only for the "value" column.
167
168 Returns:
169 defaultdict[str, defaultdict[str, str | None]]:
170 Names as result[metric_id][column]; unavailable names are None.
171
172 Code example:
173
174 .. doctest:: metadata.get_names
175
176 >>> from hdhelpers.metadata.conventions import MULTITSFRAME_METADATA_EXAMPLE
177 >>> multitsframe.attrs = MULTITSFRAME_METADATA_EXAMPLE
178 >>> md.get_names(multitsframe)["plant_a.pressure"]["value"]
179 'Plant A pressure'
180
181 See :ref:`metadata-getter-names` for shared examples and return shapes.
182 """
183 return _by_metric_by_column(multitsframe, "name", include_inherited)
184
185
[docs]
186def get_display_names(
187 multitsframe: pd.DataFrame, *, include_inherited: bool = False
188) -> defaultdict[str, defaultdict[str, str | None]]:
189 """Read display labels for each metric and value column.
190
191 Use display_name, then name.
192
193 Args:
194 multitsframe: DataFrame with per-metric metadata in attrs.
195 include_inherited: Use inherited fields last, only for the "value" column.
196
197 Returns:
198 defaultdict[str, defaultdict[str, str | None]]:
199 Labels as result[metric_id][column]; unavailable labels are None.
200
201 See :ref:`metadata-getter-names` for shared examples and return shapes.
202 """
203 return _by_metric_by_column(multitsframe, DISPLAY_NAME_FIELDS, include_inherited)
204
205
[docs]
206def get_short_display_names(
207 multitsframe: pd.DataFrame, *, include_inherited: bool = False
208) -> defaultdict[str, defaultdict[str, str | None]]:
209 """Read short display labels for each metric and value column.
210
211 Use short_display_name, then display_name, then name.
212
213 Args:
214 multitsframe: DataFrame with per-metric metadata in attrs.
215 include_inherited: Use inherited fields last, only for the "value" column.
216
217 Returns:
218 defaultdict[str, defaultdict[str, str | None]]:
219 Labels as result[metric_id][column]; unavailable labels are None.
220
221 See :ref:`metadata-getter-names` for shared examples and return shapes.
222 """
223 return _by_metric_by_column(multitsframe, SHORT_DISPLAY_NAME_FIELDS, include_inherited)
224
225
[docs]
226def get_measurements(
227 multitsframe: pd.DataFrame, *, include_inherited: bool = False
228) -> defaultdict[str, defaultdict[str, str | None]]:
229 """Read physical quantities for each metric and value column.
230
231 For example, "temperature" is a measurement and "°C" is its unit.
232
233 Args:
234 multitsframe: DataFrame with per-metric metadata in attrs.
235 include_inherited: Use inherited fields last, only for the "value" column.
236
237 Returns:
238 defaultdict[str, defaultdict[str, str | None]]:
239 Quantities as result[metric_id][column]; unavailable quantities are None.
240
241 See :ref:`metadata-getter-values` for shared examples and return shapes.
242 """
243 return _by_metric_by_column(multitsframe, "measurement", include_inherited)
244
245
[docs]
246def get_metric_info(
247 multitsframe: pd.DataFrame, metric_info: str, *, include_inherited: bool = True
248) -> defaultdict[str, Any]:
249 """Read a metadata field for each metric.
250
251 Search the metric, its "value" column, shared "value" metadata, then inherited fields
252 (if enabled).
253
254 Args:
255 multitsframe: DataFrame with per-metric metadata in attrs.
256 metric_info: Field name or dotted path, e.g. "sensor.model".
257 include_inherited: Use inherited fields as a last fallback.
258
259 Returns:
260 defaultdict[str, Any]: Field values by metric ID; unresolved fields are None.
261
262 Code example:
263
264 .. doctest:: metadata.get_metric_info
265
266 >>> from hdhelpers.metadata.conventions import MULTITSFRAME_METADATA_EXAMPLE
267 >>> multitsframe.attrs = MULTITSFRAME_METADATA_EXAMPLE
268 >>> md.get_metric_info(multitsframe, "recording_mode")["plant_a.temperature"]
269 'on_change'
270
271 See :ref:`metadata-getter-fields` for shared examples and return shapes.
272 """
273 check_dataframe(multitsframe)
274 meta = _metadata(multitsframe, "MULTITSFRAME")
275 infos = (
276 {} if meta is None else meta.metric_infos(metric_info, include_inherited=include_inherited)
277 )
278 return defaultdict(lambda: None, infos)
279
280
281# the value dimension "value" of SERIES
282
283
[docs]
284def get_series_info(
285 series: pd.Series, value_dim_info: str, *, include_inherited: bool = True
286) -> Any:
287 """Read a metadata field for this Series.
288
289 Search the "value" column, the metric, shared "value" metadata, then inherited fields
290 (if enabled).
291
292 Args:
293 series: Series with metadata in attrs.
294 value_dim_info: Field name or dotted path, e.g. "sensor.model".
295 include_inherited: Use inherited fields as a last fallback.
296
297 Returns:
298 Any: Field value, or None when unavailable.
299
300 Code example:
301
302 .. doctest:: metadata.get_series_info
303
304 >>> from hdhelpers.metadata.conventions import SERIES_METADATA_EXAMPLE
305 >>> series.attrs = SERIES_METADATA_EXAMPLE
306 >>> md.get_series_info(series, "decimals")
307 2
308
309 See :ref:`metadata-getter-fields` for shared examples and return shapes.
310 """
311 return _series_value(series, value_dim_info, include_inherited)
312
313
[docs]
314def get_series_unit(series: pd.Series, *, include_inherited: bool = False) -> str | None:
315 """Read this Series' unit from metadata.
316
317 Args:
318 series: Series with metadata in attrs.
319 include_inherited: Use inherited fields as a last fallback.
320
321 Returns:
322 str | None: Unit string; "" means dimensionless and None means unavailable.
323
324 Code example:
325
326 .. doctest:: metadata.get_series_unit
327
328 >>> from hdhelpers.metadata.conventions import SERIES_METADATA_EXAMPLE
329 >>> series.attrs = SERIES_METADATA_EXAMPLE
330 >>> md.get_series_unit(series)
331 'm'
332
333 See :ref:`metadata-getter-values` for shared examples and return shapes.
334 """
335 return _series_value(series, "unit", include_inherited) # type: ignore[no-any-return]
336
337
[docs]
338def get_series_name(series: pd.Series, *, include_inherited: bool = False) -> str | None:
339 """Read this Series' name from metadata.
340
341 Try the "value" column's name, then the metric's name.
342
343 Args:
344 series: Series with metadata in attrs.
345 include_inherited: Use inherited fields as a last fallback.
346
347 Returns:
348 str | None: Resolved name, or None when unavailable.
349
350 Code example:
351
352 .. doctest:: metadata.get_series_name
353
354 >>> from hdhelpers.metadata.conventions import SERIES_METADATA_EXAMPLE
355 >>> series.attrs = SERIES_METADATA_EXAMPLE
356 >>> md.get_series_name(series)
357 'Water level north'
358
359 See :ref:`metadata-getter-names` for shared examples and return shapes.
360 """
361 return _series_value(series, "name", include_inherited) # type: ignore[no-any-return]
362
363
[docs]
364def get_series_display_name(series: pd.Series, *, include_inherited: bool = False) -> str | None:
365 """Read the display label for this Series.
366
367 Use display_name, then name.
368
369 Args:
370 series: Series with metadata in attrs.
371 include_inherited: Use inherited fields as a last fallback.
372
373 Returns:
374 str | None: Resolved label, or None when unavailable.
375
376 See :ref:`metadata-getter-names` for shared examples and return shapes.
377 """
378 return _series_value( # type: ignore[no-any-return]
379 series, DISPLAY_NAME_FIELDS, include_inherited
380 )
381
382
[docs]
383def get_series_short_display_name(
384 series: pd.Series, *, include_inherited: bool = False
385) -> str | None:
386 """Read the short display label for this Series.
387
388 Use short_display_name, then display_name, then name.
389
390 Args:
391 series: Series with metadata in attrs.
392 include_inherited: Use inherited fields as a last fallback.
393
394 Returns:
395 str | None: Resolved label, or None when unavailable.
396
397 See :ref:`metadata-getter-names` for shared examples and return shapes.
398 """
399 return _series_value( # type: ignore[no-any-return]
400 series, SHORT_DISPLAY_NAME_FIELDS, include_inherited
401 )
402
403
[docs]
404def get_series_measurement(series: pd.Series, *, include_inherited: bool = False) -> str | None:
405 """Read this Series' physical quantity from metadata.
406
407 For example, "temperature" describes the quantity and "°C" describes its unit.
408
409 Args:
410 series: Series with metadata in attrs.
411 include_inherited: Use inherited fields as a last fallback.
412
413 Returns:
414 str | None: Quantity name, or None when unavailable.
415
416 See :ref:`metadata-getter-values` for shared examples and return shapes.
417 """
418 return _series_value(series, "measurement", include_inherited) # type: ignore[no-any-return]
419
420
421# value dimensions of SINGLETSFRAMEs
422
423
[docs]
424def get_singlets_info(
425 singletsframe: pd.DataFrame, value_dim_info: str, *, include_inherited: bool = True
426) -> defaultdict[str, Any]:
427 """Read a metadata field for each value column of a single-metric frame.
428
429 For "value", search column, metric, shared and inherited metadata (if enabled), in that
430 order. Other columns use their own and shared metadata.
431
432 Args:
433 singletsframe: DataFrame with one metric's metadata in attrs.
434 value_dim_info: Field name or dotted path, e.g. "sensor.model".
435 include_inherited: Use inherited fields last, only for the "value" column.
436
437 Returns:
438 defaultdict[str, Any]: Field values as result[column]; unresolved fields are None.
439
440 Code example:
441
442 .. doctest:: metadata.get_singlets_info
443
444 >>> from hdhelpers.metadata.conventions import SINGLETSFRAME_METADATA_EXAMPLE
445 >>> dataframe.attrs = SINGLETSFRAME_METADATA_EXAMPLE
446 >>> md.get_singlets_info(dataframe, "name")["state"]
447 'measurement state'
448
449 See :ref:`metadata-getter-fields` for shared examples and return shapes.
450 """
451 return _single_metric_by_column(singletsframe, value_dim_info, include_inherited)
452
453
[docs]
454def get_singlets_units(
455 singletsframe: pd.DataFrame, *, include_inherited: bool = False
456) -> defaultdict[str, str | None]:
457 """Read units for each value column of a single-metric frame.
458
459 Args:
460 singletsframe: DataFrame with one metric's metadata in attrs.
461 include_inherited: Use inherited fields last, only for the "value" column.
462
463 Returns:
464 defaultdict[str, str | None]:
465 Units as result[column]; "" means dimensionless and None means unavailable.
466
467 Code example:
468
469 .. doctest:: metadata.get_singlets_units
470
471 >>> from hdhelpers.metadata.conventions import SINGLETSFRAME_METADATA_EXAMPLE
472 >>> dataframe.attrs = SINGLETSFRAME_METADATA_EXAMPLE
473 >>> md.get_singlets_units(dataframe)["value"]
474 '°C'
475
476 See :ref:`metadata-getter-values` for shared examples and return shapes.
477 """
478 return _single_metric_by_column(singletsframe, "unit", include_inherited)
479
480
[docs]
481def get_singlets_names(
482 singletsframe: pd.DataFrame, *, include_inherited: bool = False
483) -> defaultdict[str, str | None]:
484 """Read metadata names for each value column of a single-metric frame.
485
486 For "value", try the metric's name after the column's own name.
487
488 Args:
489 singletsframe: DataFrame with one metric's metadata in attrs.
490 include_inherited: Use inherited fields last, only for the "value" column.
491
492 Returns:
493 defaultdict[str, str | None]: Names as result[column]; unavailable names are None.
494
495 See :ref:`metadata-getter-names` for shared examples and return shapes.
496 """
497 return _single_metric_by_column(singletsframe, "name", include_inherited)
498
499
[docs]
500def get_singlets_display_names(
501 singletsframe: pd.DataFrame, *, include_inherited: bool = False
502) -> defaultdict[str, str | None]:
503 """Read display labels for each value column of a single-metric frame.
504
505 Use display_name, then name.
506
507 Args:
508 singletsframe: DataFrame with one metric's metadata in attrs.
509 include_inherited: Use inherited fields last, only for the "value" column.
510
511 Returns:
512 defaultdict[str, str | None]: Labels as result[column]; unavailable labels are None.
513
514 See :ref:`metadata-getter-names` for shared examples and return shapes.
515 """
516 return _single_metric_by_column(singletsframe, DISPLAY_NAME_FIELDS, include_inherited)
517
518
[docs]
519def get_singlets_short_display_names(
520 singletsframe: pd.DataFrame, *, include_inherited: bool = False
521) -> defaultdict[str, str | None]:
522 """Read short display labels for each value column of a single-metric frame.
523
524 Use short_display_name, then display_name, then name.
525
526 Args:
527 singletsframe: DataFrame with one metric's metadata in attrs.
528 include_inherited: Use inherited fields last, only for the "value" column.
529
530 Returns:
531 defaultdict[str, str | None]: Labels as result[column]; unavailable labels are None.
532
533 See :ref:`metadata-getter-names` for shared examples and return shapes.
534 """
535 return _single_metric_by_column(singletsframe, SHORT_DISPLAY_NAME_FIELDS, include_inherited)
536
537
[docs]
538def get_singlets_measurements(
539 singletsframe: pd.DataFrame, *, include_inherited: bool = False
540) -> defaultdict[str, str | None]:
541 """Read physical quantities for each value column of a single-metric frame.
542
543 For example, "temperature" is a measurement and "°C" is its unit.
544
545 Args:
546 singletsframe: DataFrame with one metric's metadata in attrs.
547 include_inherited: Use inherited fields last, only for the "value" column.
548
549 Returns:
550 defaultdict[str, str | None]:
551 Quantities as result[column]; unavailable quantities are None.
552
553 See :ref:`metadata-getter-values` for shared examples and return shapes.
554 """
555 return _single_metric_by_column(singletsframe, "measurement", include_inherited)
556
557
[docs]
558def get_singlets_metric_info(
559 singletsframe: pd.DataFrame, metric_info: str, *, include_inherited: bool = True
560) -> Any:
561 """Read one metadata field for the frame's single metric.
562
563 Search the metric, its "value" column, shared "value" metadata, then inherited fields
564 (if enabled).
565
566 Args:
567 singletsframe: DataFrame with one metric's metadata in attrs.
568 metric_info: Field name or dotted path, e.g. "sensor.model".
569 include_inherited: Use inherited fields as a last fallback.
570
571 Returns:
572 Any: One field value, or None when unavailable.
573
574 Code example:
575
576 Using :ref:`metadata-example-data`, compare a field of the metric with fields of its value
577 dimensions. See :ref:`metadata-getter-fields` for the related getters:
578
579 .. doctest:: metadata.get_singlets_metric_info
580
581 >>> md.get_singlets_metric_info(dataframe, "name")
582 'ABC temperature'
583 >>> md.get_singlets_info(dataframe, "name")["state"]
584 'measurement state'
585 """
586 check_dataframe(singletsframe)
587 return _single_metric_or_dataset(
588 singletsframe,
589 "SINGLETSFRAME",
590 lambda meta, metric: meta.metric_info(
591 metric_info, metric, include_inherited=include_inherited
592 ),
593 lambda _meta: None,
594 empty=None,
595 )
596
597
598# queried interval, time zones, recording and aggregation
599
600
[docs]
601def get_queried_interval(
602 data: pd.Series | pd.DataFrame,
603) -> tuple[datetime.datetime | None, datetime.datetime | None]:
604 """Read the requested UTC time interval from dataset metadata.
605
606 Args:
607 data: Series or DataFrame with dataset metadata in attrs.
608
609 Returns:
610 tuple[datetime.datetime | None, datetime.datetime | None]:
611 (start, end) as timezone-aware datetimes, or (None, None) if unavailable.
612
613 Code example:
614
615 .. doctest:: metadata.get_queried_interval
616
617 >>> from hdhelpers.metadata.conventions import SERIES_METADATA_EXAMPLE
618 >>> series.attrs = SERIES_METADATA_EXAMPLE
619 >>> start, end = md.get_queried_interval(series)
620 >>> start.isoformat()
621 '2026-01-01T00:00:00+00:00'
622
623 See :ref:`metadata-getter-values` for shared examples and return shapes.
624 """
625 check_series_or_dataframe(data)
626 meta = _metadata(data, infer_data_type(data))
627 return (None, None) if meta is None else meta.queried_interval()
628
629
[docs]
630def get_default_timezones(multitsframe: pd.DataFrame) -> defaultdict[str, str | None]:
631 """Read the time zone recommended for calculations on each metric.
632
633 Use the metric's default_timezone, then the dataset's default_timezone.
634
635 Args:
636 multitsframe: DataFrame with per-metric metadata in attrs.
637
638 Returns:
639 defaultdict[str, str | None]:
640 Zone names or UTC offsets by metric ID, or None for unresolved zones. Missing
641 IDs use the dataset default.
642
643 Code example:
644
645 .. doctest:: metadata.get_default_timezones
646
647 >>> from hdhelpers.metadata.conventions import MULTITSFRAME_METADATA_EXAMPLE
648 >>> multitsframe.attrs = MULTITSFRAME_METADATA_EXAMPLE
649 >>> md.get_default_timezones(multitsframe)["plant_a.pressure"]
650 'Europe/Berlin'
651
652 See :ref:`metadata-getter-timezones` for shared examples and return shapes.
653 """
654 check_dataframe(multitsframe)
655 meta = _metadata(multitsframe, "MULTITSFRAME")
656 if meta is None:
657 return defaultdict(lambda: None)
658 dataset_timezone = dataset_default_timezone(meta)
659 return defaultdict(lambda: dataset_timezone, meta.default_timezones())
660
661
[docs]
662def get_series_default_timezone(series: pd.Series) -> str | None:
663 """Read the time zone recommended for calculations on this Series.
664
665 Use the metric's default_timezone, then the dataset's default_timezone.
666
667 Args:
668 series: Series with metadata in attrs.
669
670 Returns:
671 str | None: Zone name or UTC offset, or None when neither source provides one.
672
673 See :ref:`metadata-getter-timezones` for shared examples and return shapes.
674 """
675 check_series(series)
676 return _single_metric_or_dataset( # type: ignore[no-any-return]
677 series,
678 "SERIES",
679 lambda meta, metric: meta.default_timezone(metric),
680 dataset_default_timezone,
681 empty=None,
682 )
683
684
[docs]
685def get_singlets_default_timezone(singletsframe: pd.DataFrame) -> str | None:
686 """Read the time zone recommended for calculations on a single-metric frame.
687
688 Use the metric's default_timezone, then the dataset's default_timezone.
689
690 Args:
691 singletsframe: DataFrame with one metric's metadata in attrs.
692
693 Returns:
694 str | None: Zone name or UTC offset, or None when neither source provides one.
695
696 See :ref:`metadata-getter-timezones` for shared examples and return shapes.
697 """
698 check_dataframe(singletsframe)
699 return _single_metric_or_dataset( # type: ignore[no-any-return]
700 singletsframe,
701 "SINGLETSFRAME",
702 lambda meta, metric: meta.default_timezone(metric),
703 dataset_default_timezone,
704 empty=None,
705 )
706
707
[docs]
708def get_recordings(multitsframe: pd.DataFrame) -> defaultdict[str, Recording]:
709 """Read recording settings for each metric.
710
711 Use the metric's frequency, offset and time zone together. If its frequency is missing,
712 use the dataset's three fields instead.
713
714 Args:
715 multitsframe: DataFrame with per-metric metadata in attrs.
716
717 Returns:
718 defaultdict[str, Recording]:
719 Settings by metric ID; missing IDs use dataset frequency settings.
720
721 Code example:
722
723 .. doctest:: metadata.get_recordings
724
725 >>> from hdhelpers.metadata.conventions import MULTITSFRAME_METADATA_EXAMPLE
726 >>> multitsframe.attrs = MULTITSFRAME_METADATA_EXAMPLE
727 >>> md.get_recordings(multitsframe)["plant_a.temperature"].recording_mode
728 'on_change'
729
730 See :ref:`metadata-getter-recordings` for shared examples and return shapes.
731 """
732 check_dataframe(multitsframe)
733 meta = _metadata(multitsframe, "MULTITSFRAME")
734 if meta is None:
735 return defaultdict(Recording)
736 no_metadata = dataset_recording(meta)
737 return defaultdict(lambda: no_metadata, meta.recordings())
738
739
[docs]
740def get_series_recording(series: pd.Series) -> Recording:
741 """Read recording settings for this Series.
742
743 Use the metric's frequency, offset and time zone together. If its frequency is missing,
744 use the dataset's three fields instead.
745
746 Args:
747 series: Series with metadata in attrs.
748
749 Returns:
750 Recording: Recording object; absent metadata yields its default field values.
751
752 See :ref:`metadata-getter-recordings` for shared examples and return shapes.
753 """
754 check_series(series)
755 return _single_metric_or_dataset( # type: ignore[no-any-return]
756 series,
757 "SERIES",
758 lambda meta, metric: meta.recording(metric),
759 dataset_recording,
760 empty=Recording(),
761 )
762
763
[docs]
764def get_singlets_recording(singletsframe: pd.DataFrame) -> Recording:
765 """Read recording settings for a single-metric frame.
766
767 Use the metric's frequency, offset and time zone together. If its frequency is missing,
768 use the dataset's three fields instead.
769
770 Args:
771 singletsframe: DataFrame with one metric's metadata in attrs.
772
773 Returns:
774 Recording: Recording object; absent metadata yields its default field values.
775
776 See :ref:`metadata-getter-recordings` for shared examples and return shapes.
777 """
778 check_dataframe(singletsframe)
779 return _single_metric_or_dataset( # type: ignore[no-any-return]
780 singletsframe,
781 "SINGLETSFRAME",
782 lambda meta, metric: meta.recording(metric),
783 dataset_recording,
784 empty=Recording(),
785 )
786
787
[docs]
788def get_default_aggregation_methods(multitsframe: pd.DataFrame) -> defaultdict[str, str]:
789 """Read the default aggregation method for each metric.
790
791 Use explicit default_aggregation_method (AVG becomes MEAN). Otherwise, metadata
792 value_data_type selects LAST for boolean/string and MEAN for other types.
793
794 Args:
795 multitsframe: DataFrame with per-metric metadata in attrs.
796
797 Returns:
798 defaultdict[str, str]: Method names by metric ID; missing IDs default to MEAN.
799
800 Code example:
801
802 .. doctest:: metadata.get_default_aggregation_methods
803
804 >>> from hdhelpers.metadata.conventions import MULTITSFRAME_METADATA_EXAMPLE
805 >>> multitsframe.attrs = MULTITSFRAME_METADATA_EXAMPLE
806 >>> methods = md.get_default_aggregation_methods(multitsframe)
807 >>> methods["plant_a.temperature"], methods["plant_a.pressure"]
808 ('MAX', 'MEAN')
809
810 See :ref:`metadata-getter-aggregation` for shared examples and return shapes.
811 """
812 check_dataframe(multitsframe)
813 meta = _metadata(multitsframe, "MULTITSFRAME")
814 methods = {} if meta is None else meta.default_aggregation_methods()
815 return defaultdict(lambda: _DEFAULT_AGGREGATION_METHOD, methods)
816
817
[docs]
818def get_series_default_aggregation_method(series: pd.Series) -> str:
819 """Read the default aggregation method for this Series.
820
821 Use explicit default_aggregation_method (AVG becomes MEAN). Otherwise, metadata
822 value_data_type selects LAST for boolean/string and MEAN for other types.
823
824 Args:
825 series: Series with metadata in attrs.
826
827 Returns:
828 str: Method name; MEAN if metadata is unavailable.
829
830 See :ref:`metadata-getter-aggregation` for shared examples and return shapes.
831 """
832 check_series(series)
833 return _single_metric_or_dataset( # type: ignore[no-any-return]
834 series,
835 "SERIES",
836 lambda meta, metric: meta.default_aggregation_method(metric),
837 lambda _meta: _DEFAULT_AGGREGATION_METHOD,
838 empty=_DEFAULT_AGGREGATION_METHOD,
839 )
840
841
[docs]
842def get_singlets_default_aggregation_method(singletsframe: pd.DataFrame) -> str:
843 """Read the default aggregation method for a single-metric frame.
844
845 Use explicit default_aggregation_method (AVG becomes MEAN). Otherwise, metadata
846 value_data_type selects LAST for boolean/string and MEAN for other types.
847
848 Args:
849 singletsframe: DataFrame with one metric's metadata in attrs.
850
851 Returns:
852 str: Method name; MEAN if metadata is unavailable.
853
854 See :ref:`metadata-getter-aggregation` for shared examples and return shapes.
855 """
856 check_dataframe(singletsframe)
857 return _single_metric_or_dataset( # type: ignore[no-any-return]
858 singletsframe,
859 "SINGLETSFRAME",
860 lambda meta, metric: meta.default_aggregation_method(metric),
861 lambda _meta: _DEFAULT_AGGREGATION_METHOD,
862 empty=_DEFAULT_AGGREGATION_METHOD,
863 )
864
865