labda 0.0.2__tar.gz

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 (46) hide show
  1. labda-0.0.2/CHANGELOG.md +16 -0
  2. labda-0.0.2/PKG-INFO +59 -0
  3. labda-0.0.2/README.md +26 -0
  4. labda-0.0.2/labda/__init__.py +1 -0
  5. labda-0.0.2/labda/assets/__init__.py +2 -0
  6. labda-0.0.2/labda/assets/counts_cut_points.py +35 -0
  7. labda-0.0.2/labda/assets/transportation_cut_points.py +16 -0
  8. labda-0.0.2/labda/expanders/__init__.py +8 -0
  9. labda-0.0.2/labda/expanders/accelerometer.py +29 -0
  10. labda-0.0.2/labda/expanders/extras.py +33 -0
  11. labda-0.0.2/labda/expanders/spatial.py +242 -0
  12. labda-0.0.2/labda/parsers/__init__.py +4 -0
  13. labda-0.0.2/labda/parsers/actigraph.py +232 -0
  14. labda-0.0.2/labda/parsers/bulk.py +31 -0
  15. labda-0.0.2/labda/parsers/gadgetbridge.py +232 -0
  16. labda-0.0.2/labda/parsers/garmin.py +89 -0
  17. labda-0.0.2/labda/parsers/qstarz.py +366 -0
  18. labda-0.0.2/labda/processing/__init__.py +1 -0
  19. labda-0.0.2/labda/processing/accelerometer/__init__.py +2 -0
  20. labda-0.0.2/labda/processing/accelerometer/activity_intensity.py +57 -0
  21. labda-0.0.2/labda/processing/accelerometer/steps.py +24 -0
  22. labda-0.0.2/labda/processing/accelerometer/wear.py +38 -0
  23. labda-0.0.2/labda/processing/bouts.py +85 -0
  24. labda-0.0.2/labda/processing/domains.py +32 -0
  25. labda-0.0.2/labda/processing/manipulations.py +22 -0
  26. labda-0.0.2/labda/processing/spatial/__init__.py +4 -0
  27. labda-0.0.2/labda/processing/spatial/environment.py +50 -0
  28. labda-0.0.2/labda/processing/spatial/manipulations.py +186 -0
  29. labda-0.0.2/labda/processing/spatial/timeline.py +99 -0
  30. labda-0.0.2/labda/processing/spatial/transportation.py +80 -0
  31. labda-0.0.2/labda/processing/spatial/trips.py +551 -0
  32. labda-0.0.2/labda/processing/wear_validity.py +69 -0
  33. labda-0.0.2/labda/structure/__init__.py +6 -0
  34. labda-0.0.2/labda/structure/collection.py +103 -0
  35. labda-0.0.2/labda/structure/domains.py +29 -0
  36. labda-0.0.2/labda/structure/linkage.py +28 -0
  37. labda-0.0.2/labda/structure/merging.py +133 -0
  38. labda-0.0.2/labda/structure/resampling.py +82 -0
  39. labda-0.0.2/labda/structure/subject.py +294 -0
  40. labda-0.0.2/labda/structure/validation/__init__.py +0 -0
  41. labda-0.0.2/labda/structure/validation/domains.py +37 -0
  42. labda-0.0.2/labda/structure/validation/linkage.py +28 -0
  43. labda-0.0.2/labda/structure/validation/subject.py +323 -0
  44. labda-0.0.2/labda/utils.py +240 -0
  45. labda-0.0.2/labda/visualisation/__init__.py +0 -0
  46. labda-0.0.2/pyproject.toml +64 -0
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ - Parser: Qstarz
13
+ - Basic subject and collection manipulaton.
14
+ - Expanding data with timedelta, speed, acceleration, distance, direction.
15
+ - Getting sampling frequency, CRS and timezone automatically from dataset.
16
+ - Initial documentation with mkdocs and material theme.
labda-0.0.2/PKG-INFO ADDED
@@ -0,0 +1,59 @@
1
+ Metadata-Version: 2.1
2
+ Name: labda
3
+ Version: 0.0.2
4
+ Summary: Library for Advanced Behavioural Data Analysis
5
+ Home-page: https://labda.josefheidler.cz
6
+ License: CC-BY-SA-4.0
7
+ Keywords: analysis,health,behaviour,data,spatial,temporal
8
+ Author: Josef Heidler
9
+ Author-email: jheidler@health.sdu.dk
10
+ Maintainer: Josef Heidler
11
+ Maintainer-email: jheidler@health.sdu.dk
12
+ Requires-Python: >=3.11,<4.0
13
+ Classifier: Development Status :: 2 - Pre-Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Education
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: License :: Other/Proprietary License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Topic :: Scientific/Engineering
22
+ Requires-Dist: geopandas (>=0.14.2,<0.15.0)
23
+ Requires-Dist: openpyxl (>=3.1.2,<4.0.0)
24
+ Requires-Dist: pandas (>=2.2.0,<3.0.0)
25
+ Requires-Dist: pandera (>=0.18.0,<0.19.0)
26
+ Requires-Dist: pyarrow (>=15.0.0,<16.0.0)
27
+ Requires-Dist: pydeck (>=0.8.0,<0.9.0)
28
+ Requires-Dist: sqlalchemy (>=2.0.25,<3.0.0)
29
+ Requires-Dist: timezonefinder (>=6.2.0,<7.0.0)
30
+ Project-URL: Documentation, https://labda.josefheidler.cz
31
+ Project-URL: Repository, https://github.com/josefheidler/labda
32
+ Description-Content-Type: text/markdown
33
+
34
+ # LABDA: Library for Advanced Behavioural Data Analysis
35
+
36
+ **LABDA is currently under development and may undergo significant API changes that could result in breaking changes.**
37
+
38
+ ## What is it?
39
+
40
+ TO-DO...
41
+
42
+ ## Main Features
43
+
44
+ TO-DO...
45
+
46
+ ## License
47
+ <p xmlns:cc="http://creativecommons.org/ns#" xmlns:dct="http://purl.org/dc/terms/"><a property="dct:title" rel="cc:attributionURL" href="https://labda.josefheidler.cz/">Library for Advanced Behavioural Data Analysis</a> by <a rel="cc:attributionURL dct:creator" property="cc:attributionName" href="https://josefheidler.cz/">Josef Heidler</a> is licensed under <a href="http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1" target="_blank" rel="license noopener noreferrer" style="display:inline-block;">CC BY 4.0<img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1"><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1"></a></p>
48
+
49
+ ## Documentation
50
+
51
+ TO-DO...
52
+
53
+ ## Getting Help, Discussion and Development
54
+
55
+ TO-DO...
56
+
57
+ ## Contributing to labda
58
+
59
+ TO-DO..
labda-0.0.2/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # LABDA: Library for Advanced Behavioural Data Analysis
2
+
3
+ **LABDA is currently under development and may undergo significant API changes that could result in breaking changes.**
4
+
5
+ ## What is it?
6
+
7
+ TO-DO...
8
+
9
+ ## Main Features
10
+
11
+ TO-DO...
12
+
13
+ ## License
14
+ <p xmlns:cc="http://creativecommons.org/ns#" xmlns:dct="http://purl.org/dc/terms/"><a property="dct:title" rel="cc:attributionURL" href="https://labda.josefheidler.cz/">Library for Advanced Behavioural Data Analysis</a> by <a rel="cc:attributionURL dct:creator" property="cc:attributionName" href="https://josefheidler.cz/">Josef Heidler</a> is licensed under <a href="http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1" target="_blank" rel="license noopener noreferrer" style="display:inline-block;">CC BY 4.0<img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1"><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1"></a></p>
15
+
16
+ ## Documentation
17
+
18
+ TO-DO...
19
+
20
+ ## Getting Help, Discussion and Development
21
+
22
+ TO-DO...
23
+
24
+ ## Contributing to labda
25
+
26
+ TO-DO..
@@ -0,0 +1 @@
1
+ from .structure import Collection, Column, Subject, merge_collections, merge_subjects
@@ -0,0 +1,2 @@
1
+ from .counts_cut_points import CUT_POINTS as COUNTS_CUT_POINTS
2
+ from .transportation_cut_points import CUT_POINTS as TRANSPORTATION_CUT_POINTS
@@ -0,0 +1,35 @@
1
+ from ..structure.validation.subject import Column
2
+
3
+ CUT_POINTS = [
4
+ {
5
+ "id": 1,
6
+ "name": "Freedson Adult (1998)",
7
+ "reference": "https://journals.lww.com/acsm-msse/fulltext/1998/05000/calibration_of_the_computer_science_and.21.aspx",
8
+ "sampling_frequency": 60,
9
+ "category": "adult",
10
+ "placement": "hip",
11
+ "required_data": Column.VERTICAL_COUNTS,
12
+ "cut_points": [
13
+ {"name": "sedentary", "max": 99},
14
+ {"name": "light", "max": 1951},
15
+ {"name": "moderate", "max": 5724},
16
+ {"name": "vigorous", "max": 9498},
17
+ {"name": "very_vigorous", "max": float("inf")},
18
+ ],
19
+ },
20
+ {
21
+ "id": 2,
22
+ "name": "Freedson Adult VM3 (2011)",
23
+ "reference": "https://doi.org/10.1016/j.jsams.2011.04.003",
24
+ "sampling_frequency": 60,
25
+ "category": "adult",
26
+ "placement": "hip",
27
+ "required_data": Column.VM_COUNTS,
28
+ "cut_points": [
29
+ {"name": "light", "max": 2689},
30
+ {"name": "moderate", "max": 6166},
31
+ {"name": "vigorous", "max": 9642},
32
+ {"name": "very_vigorous", "max": float("inf")},
33
+ ],
34
+ },
35
+ ]
@@ -0,0 +1,16 @@
1
+ from ..structure.validation.subject import Column
2
+
3
+ CUT_POINTS = [
4
+ {
5
+ "id": 1,
6
+ "name": "Carlson PALMS Validation (2015)",
7
+ "reference": "https://doi.org/10.1249/MSS.0000000000000446",
8
+ "required_data": Column.SPEED,
9
+ "units": "km/h",
10
+ "cut_points": [
11
+ {"name": "walking/running", "max": 9},
12
+ {"name": "bicycle", "max": 35},
13
+ {"name": "vehicle", "max": float("inf")},
14
+ ],
15
+ },
16
+ ]
@@ -0,0 +1,8 @@
1
+ from .extras import add_timedelta
2
+ from .spatial import (
3
+ add_acceleration,
4
+ add_direction,
5
+ add_distance,
6
+ add_elevation,
7
+ add_speed,
8
+ )
@@ -0,0 +1,29 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ from ..structure.validation.subject import Column
5
+ from ..utils import columns_exists, columns_not_exists
6
+
7
+
8
+ def add_counts_vector_magnitude(
9
+ df: pd.DataFrame,
10
+ *,
11
+ name: str = Column.VM_COUNTS,
12
+ overwrite: bool = False,
13
+ ) -> pd.DataFrame:
14
+ counts_cols = [
15
+ Column.VERTICAL_COUNTS,
16
+ Column.HORIZONTAL_COUNTS,
17
+ Column.PERPENDICULAR_COUNTS,
18
+ ]
19
+
20
+ columns_exists(df, counts_cols) # type: ignore
21
+ columns_not_exists(df, [name], overwrite=overwrite)
22
+
23
+ df = df.copy()
24
+ df[name] = np.linalg.norm(
25
+ df[counts_cols].astype(float),
26
+ axis=1,
27
+ )
28
+
29
+ return df
@@ -0,0 +1,33 @@
1
+ import pandas as pd
2
+
3
+ from ..structure.validation.subject import Column
4
+ from ..utils import columns_not_exists
5
+
6
+
7
+ def add_timedelta(
8
+ df: pd.DataFrame,
9
+ *,
10
+ name: str = Column.TIMEDELTA,
11
+ overwrite: bool = False,
12
+ ) -> pd.DataFrame:
13
+ """
14
+ Adds a timedelta column to the DataFrame.
15
+
16
+ This function calculates the time difference between each row and the previous row and adds it as a new column to the DataFrame.
17
+
18
+ Args:
19
+ df (pd.DataFrame): The DataFrame to which to add the timedelta column.
20
+ name (str, optional): The name of the new timedelta column. Defaults to "timedelta".
21
+ overwrite (bool, optional): Whether to overwrite the existing timedelta column if it exists. Defaults to False.
22
+
23
+ Returns:
24
+ pd.DataFrame: The DataFrame with the added timedelta column.
25
+ """
26
+
27
+ columns_not_exists(df, [name], overwrite=overwrite)
28
+
29
+ df = df.copy()
30
+
31
+ df[name] = df.index.diff() # type: ignore
32
+
33
+ return df
@@ -0,0 +1,242 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ from ..structure.validation.subject import Column
5
+ from ..utils import (
6
+ columns_exists,
7
+ columns_not_exists,
8
+ convert_to_geodataframe,
9
+ get_crs,
10
+ get_crs_units,
11
+ get_unique_column_name,
12
+ )
13
+ from .extras import add_timedelta
14
+
15
+ __all__ = [
16
+ "add_speed",
17
+ "add_distance",
18
+ "add_acceleration",
19
+ "add_direction",
20
+ "add_elevation",
21
+ # "add_weather",
22
+ # "add_address",
23
+ ]
24
+
25
+
26
+ def add_speed(
27
+ df: pd.DataFrame,
28
+ *,
29
+ crs: str | None = None,
30
+ name: str = Column.SPEED,
31
+ overwrite: bool = False,
32
+ ) -> pd.DataFrame:
33
+ """
34
+ Adds a speed column to the DataFrame by calculating the speed based on the distance and time.
35
+ Units are based on the CRS. If CRS is not specified, the CRS will be guessed based on the coordinates (WGS84 by default).
36
+
37
+ Args:
38
+ df (pd.DataFrame): The input DataFrame.
39
+ crs (str | None, optional): The coordinate reference system. Defaults to None.
40
+ name (str, optional): The name of the speed dataframe.Column. Defaults to "speed".
41
+ overwrite (bool, optional): Whether to overwrite the existing speed column if it already exists.
42
+ Defaults to False.
43
+
44
+ Returns:
45
+ pd.DataFrame: The DataFrame with the speed column added.
46
+
47
+ Raises:
48
+ ValueError: If the speed column already exists in the DataFrame and overwrite is set to False.
49
+ """
50
+
51
+ columns_not_exists(df, [name], overwrite=overwrite)
52
+
53
+ df = df.copy()
54
+
55
+ timedelta_colname = get_unique_column_name(df)
56
+ df = add_timedelta(df, name=timedelta_colname)
57
+
58
+ distance_colname = get_unique_column_name(df)
59
+ df = add_distance(df, crs=crs, name=distance_colname)
60
+
61
+ df[name] = df[distance_colname] / df[timedelta_colname].dt.total_seconds()
62
+
63
+ # TODO: This is temporary fix for changing m/s to km/h for now.
64
+ if crs:
65
+ units = get_crs_units(crs)
66
+
67
+ if units == "metre":
68
+ df[name] = df[name] * 3.6
69
+
70
+ return df.drop(columns=[timedelta_colname, distance_colname])
71
+
72
+
73
+ def add_distance(
74
+ df: pd.DataFrame,
75
+ *,
76
+ crs: str | None = None,
77
+ name: str = Column.DISTANCE,
78
+ overwrite: bool = False,
79
+ ) -> pd.DataFrame:
80
+ """
81
+ Adds a column to the DataFrame with the distance between consecutive points.
82
+ Units are based on the CRS. If CRS is not specified, the CRS will be guessed based on the coordinates (WGS84 by default).
83
+
84
+ Args:
85
+ df (pd.DataFrame): The input DataFrame.
86
+ crs (str | None, optional): The coordinate reference system of the DataFrame. Defaults to None.
87
+ name (str, optional): The name of the new dataframe.Column. Defaults to "distance".
88
+ overwrite (bool, optional): Whether to overwrite the existing column with the same name. Defaults to False.
89
+
90
+ Returns:
91
+ pd.DataFrame: The DataFrame with the added distance dataframe.Column.
92
+
93
+ Raises:
94
+ ValueError: If the column with the specified name already exists in the DataFrame and overwrite is False.
95
+ """
96
+
97
+ columns_exists(df, [Column.LATITUDE, Column.LONGITUDE])
98
+ columns_not_exists(df, [name], overwrite=overwrite)
99
+
100
+ df = df.copy()
101
+ gdf = convert_to_geodataframe(df, crs=crs)
102
+ if gdf.crs is None:
103
+ crs = get_crs(gdf)
104
+ gdf = gdf.set_crs(crs)
105
+
106
+ df[name] = gdf.distance(gdf.shift(1)) # type: ignore
107
+
108
+ return df
109
+
110
+
111
+ def add_acceleration(
112
+ df: pd.DataFrame,
113
+ *,
114
+ crs: str | None = None,
115
+ name: str = Column.ACCELERATION,
116
+ overwrite: bool = False,
117
+ ) -> pd.DataFrame:
118
+ """
119
+ Adds a column to the DataFrame with the acceleration between consecutive points.
120
+ Acceleration is calculated based on the change in speed over time.
121
+ Units are based on the CRS. If CRS is not specified, the CRS will be guessed based on the coordinates (WGS84 by default).
122
+
123
+ Args:
124
+ df (pd.DataFrame): The input DataFrame.
125
+ crs (str | None, optional): The coordinate reference system of the DataFrame. Defaults to None.
126
+ name (str, optional): The name of the new dataframe.Column. Defaults to "acceleration".
127
+ overwrite (bool, optional): Whether to overwrite the existing column with the same name. Defaults to False.
128
+
129
+ Returns:
130
+ pd.DataFrame: The DataFrame with the added acceleration dataframe.Column.
131
+
132
+ Raises:
133
+ ValueError: If the column with the specified name already exists in the DataFrame and overwrite is False.
134
+ """
135
+
136
+ # TODO: Needs to be tested.
137
+ columns_not_exists(df, [name], overwrite=overwrite)
138
+
139
+ df = df.copy()
140
+
141
+ speed_colname = get_unique_column_name(df)
142
+ df = add_speed(df, crs=crs, name=speed_colname)
143
+
144
+ timedelta_colname = get_unique_column_name(df)
145
+ df = add_timedelta(df, name=timedelta_colname)
146
+
147
+ df[name] = (df[speed_colname] - df[speed_colname].shift()) / df[
148
+ timedelta_colname
149
+ ].dt.total_seconds()
150
+
151
+ return df.drop(columns=[speed_colname, timedelta_colname])
152
+
153
+
154
+ def add_direction(
155
+ df: pd.DataFrame,
156
+ *,
157
+ name: str = Column.DIRECTION,
158
+ overwrite: bool = False,
159
+ ) -> pd.DataFrame:
160
+ """
161
+ Adds a column to the DataFrame with the direction between consecutive points.
162
+ Direction is calculated based on the change in longitude and latitude.
163
+
164
+ Args:
165
+ df (pd.DataFrame): The input DataFrame.
166
+ name (str, optional): The name of the new dataframe.Column. Defaults to "direction".
167
+ overwrite (bool, optional): Whether to overwrite the existing column with the same name. Defaults to False.
168
+
169
+ Returns:
170
+ pd.DataFrame: The DataFrame with the added direction dataframe.Column.
171
+
172
+ Raises:
173
+ ValueError: If the column with the specified name already exists in the DataFrame and overwrite is False.
174
+ """
175
+
176
+ # TODO: Needs to be tested.
177
+ columns_exists(df, [Column.LATITUDE, Column.LONGITUDE])
178
+ columns_not_exists(df, [name], overwrite=overwrite)
179
+
180
+ df = df.copy()
181
+
182
+ # Convert to radians
183
+ lon1, lat1 = np.radians(df[Column.LONGITUDE]), np.radians(df[Column.LATITUDE])
184
+ lon2, lat2 = (
185
+ np.radians(df[Column.LONGITUDE].shift()),
186
+ np.radians(df[Column.LATITUDE].shift()),
187
+ )
188
+
189
+ dlon = lon2 - lon1
190
+
191
+ x = np.cos(lat2) * np.sin(dlon)
192
+ y = np.cos(lat1) * np.sin(lat2) - np.sin(lat1) * np.cos(lat2) * np.cos(dlon)
193
+
194
+ direction = np.arctan2(x, y)
195
+ direction = np.degrees(direction)
196
+ direction = (direction + 360) % 360
197
+
198
+ df[name] = direction
199
+
200
+ return df
201
+
202
+
203
+ def add_elevation(
204
+ df: pd.DataFrame,
205
+ *,
206
+ name: str = Column.ELEVATION,
207
+ overwrite: bool = False,
208
+ ) -> pd.DataFrame:
209
+ # TODO: Add elevation.
210
+
211
+ columns_not_exists(df, [name], overwrite=overwrite)
212
+
213
+ raise NotImplementedError("This method needs to be implemented.")
214
+
215
+
216
+ # def add_weather(
217
+ # df: pd.DataFrame,
218
+ # *,
219
+ # name: str = "weather",
220
+ # overwrite: bool = False,
221
+ # ) -> pd.DataFrame:
222
+ # # TODO: https://open-meteo.com/en/docs/historical-weather-api/
223
+ # # TODO: Add weather.
224
+
225
+ # if not overwrite:
226
+ # check_columns_existence(df, [name], exists=False)
227
+
228
+ # raise NotImplementedError("This method needs to be implemented.")
229
+
230
+
231
+ # def add_address(
232
+ # df: pd.DataFrame,
233
+ # *,
234
+ # name: str = "address",
235
+ # overwrite: bool = False,
236
+ # ) -> pd.DataFrame:
237
+ # # TODO: Add address.
238
+
239
+ # if not overwrite:
240
+ # check_columns_existence(df, [name], exists=False)
241
+
242
+ # raise NotImplementedError("This method needs to be implemented.")
@@ -0,0 +1,4 @@
1
+ from . import gadgetbridge as Gadgetbridge
2
+ from . import qstarz as Qstarz
3
+ from . import actigraph as Actigraph
4
+ from .bulk import bulk_parser