seabirdfilehandler 0.4.0__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.

Potentially problematic release.


This version of seabirdfilehandler might be problematic. Click here for more details.

@@ -0,0 +1,5 @@
1
+ from .seabirdfiles import *
2
+ from .datatablefiles import *
3
+ from .xmlfiles import *
4
+ from .validation_modules import *
5
+ from .file_collection import *
@@ -0,0 +1,184 @@
1
+ import pandas as pd
2
+ import logging
3
+ from pandas.api.extensions import register_series_accessor
4
+ from pandas.api.extensions import register_dataframe_accessor
5
+ import warnings
6
+
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class MetadataHandler:
12
+ """
13
+ The base class for the pandas series and dataframe accessors.
14
+ Offers a very basic metadata handling, by using a dictionary as metadata
15
+ store. The accessors then allow to access this metadata store and
16
+ corresponding methods by calling 'df.meta' or 'series.meta', respectively.
17
+ Mainly targeted for usage with dataframes featuring data from CNV files,
18
+ it for example allows the attachement of parameter metadata found in the
19
+ CNV header to individual dataframe columns.
20
+
21
+ This approach was chosen over others, like directly subclassing the pandas
22
+ dataframe or series class, or a seperate metadata storage, due to its
23
+ simplicity and ability to keep using the full powerfull pandas library
24
+ without the need to implement each and every transformation. Of course,
25
+ the 'attrs' attribute does offer a similar metadata storage. But at the
26
+ time of writing this, it is still in a very experimental condition and does
27
+ not propagate reliably.
28
+ """
29
+
30
+ def __init__(self, pandas_obj):
31
+ self._obj = pandas_obj
32
+ if not hasattr(self._obj, "_metadata_store"):
33
+ with warnings.catch_warnings():
34
+ warnings.simplefilter("ignore")
35
+ self._obj._metadata_store = {}
36
+
37
+ @property
38
+ def metadata(self):
39
+ return self._obj._metadata_store
40
+
41
+ @metadata.setter
42
+ def metadata(self, value):
43
+ self._obj._metadata_store = value
44
+
45
+ def get(self, key, default=None):
46
+ return self._obj._metadata_store.get(key, default)
47
+
48
+ def set(self, key, value):
49
+ self._obj._metadata_store[key] = value
50
+
51
+ def clear(self):
52
+ self._obj._metadata_store.clear()
53
+
54
+
55
+ @register_series_accessor("meta")
56
+ class SeriesMetaAccessor(MetadataHandler):
57
+ """
58
+ Series implementation of the Metadata Accessor.
59
+ Does not offer anything more than the base class at the moment.
60
+ """
61
+
62
+ def __init__(self, pandas_obj):
63
+ super().__init__(pandas_obj)
64
+
65
+
66
+ @register_dataframe_accessor("meta")
67
+ class DataFrameMetaAccessor(MetadataHandler):
68
+ """
69
+ DataFrame implementation of the Metadata Accessor.
70
+ Introduces another attribute, '_header_level_detail', that stores the
71
+ currently displayed metadata as column names. Additionally offers methods
72
+ to sync metadata between the dataframe and its series, and the handling of
73
+ common operations, like renaming or the addition of new columns.
74
+ """
75
+
76
+ def __init__(self, pandas_obj):
77
+ super().__init__(pandas_obj)
78
+ if not hasattr(self._obj, "_header_level_detail"):
79
+ self._obj._header_level_detail = "shortname"
80
+ # Initialize DataFrame metadata
81
+ self.aggregate_series_metadata()
82
+
83
+ @property
84
+ def header_detail(self):
85
+ return self._obj._header_level_detail
86
+
87
+ @header_detail.setter
88
+ def header_detail(self, value):
89
+ self._obj._header_level_detail = value
90
+
91
+ @property
92
+ def metadata(self):
93
+ return self._obj._metadata_store
94
+
95
+ @metadata.setter
96
+ def metadata(self, value):
97
+ meta_dict = {
98
+ shortname: self.add_default_metadata(shortname, metainfo)
99
+ for shortname, metainfo in value.items()
100
+ }
101
+ self._obj._metadata_store = meta_dict
102
+ self.propagate_metadata_to_series()
103
+
104
+ def aggregate_series_metadata(self):
105
+ """Aggregate metadata from Series within the DataFrame."""
106
+ for column in self._obj.columns:
107
+ if isinstance(self._obj[column], pd.Series) and hasattr(
108
+ self._obj[column], "meta"
109
+ ):
110
+ self.metadata[column] = self._obj[column].meta.metadata
111
+
112
+ def propagate_metadata_to_series(self):
113
+ """Propagate DataFrame-level metadata back to Series."""
114
+ for column in self._obj.columns:
115
+ if isinstance(self._obj[column], pd.Series) and hasattr(
116
+ self._obj[column], "meta"
117
+ ):
118
+ for key, value in self.metadata.items():
119
+ if key == column:
120
+ try:
121
+ self._obj[column].meta.metadata = value
122
+ except TypeError:
123
+ logger.error(f"{column}: {value}")
124
+
125
+ def update_metadata_on_rename(self, rename_dict):
126
+ """Update metadata when columns are renamed."""
127
+ new_metadata = {}
128
+ for old_name, new_name in rename_dict.items():
129
+ for key, value in self.metadata.items():
130
+ if key == old_name:
131
+ new_metadata[new_name] = value
132
+ self.metadata = new_metadata
133
+ self.propagate_metadata_to_series()
134
+
135
+ def rename(self, rename_key):
136
+ """Rename the column names by using a metadata point."""
137
+ rename_dict = {
138
+ column: (
139
+ self._obj[column].meta.get(rename_key)
140
+ if rename_key in list(self._obj[column].meta.metadata.keys())
141
+ else column
142
+ )
143
+ for column in self._obj.columns
144
+ }
145
+ self._obj.rename(columns=rename_dict, inplace=True)
146
+ self.header_detail = rename_key
147
+ self.update_metadata_on_rename(rename_dict)
148
+
149
+ def add_column(
150
+ self,
151
+ name: str,
152
+ data: pd.Series | list,
153
+ location: int | None = None,
154
+ metadata: dict = {},
155
+ ):
156
+ """Add a column and use or generate metadata for it."""
157
+ location = len(self._obj.columns) if location is None else location
158
+ self._obj.insert(
159
+ loc=location,
160
+ column=name,
161
+ value=data,
162
+ allow_duplicates=False,
163
+ )
164
+ self.metadata[name] = self.add_default_metadata(name, metadata)
165
+ self.propagate_metadata_to_series()
166
+
167
+ def add_default_metadata(
168
+ self,
169
+ name: str,
170
+ metadata: dict = {},
171
+ list_of_keys: list = [
172
+ "shortname",
173
+ "longinfo",
174
+ "name",
175
+ "metainfo",
176
+ "unit",
177
+ ],
178
+ ) -> dict:
179
+ """Fill up missing metadata points with a default value."""
180
+ default = {}
181
+ for key in list_of_keys:
182
+ if key not in list(metadata.keys()):
183
+ default[key] = name
184
+ return {**metadata, **default}