scdata 1.0.0__tar.gz → 1.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.
- {scdata-1.0.0/scdata.egg-info → scdata-1.0.2}/PKG-INFO +5 -7
- {scdata-1.0.0 → scdata-1.0.2}/README.md +3 -5
- {scdata-1.0.0 → scdata-1.0.2}/requirements.txt +1 -1
- {scdata-1.0.0 → scdata-1.0.2}/scdata/__init__.py +1 -1
- {scdata-1.0.0 → scdata-1.0.2}/scdata/_config/config.py +2 -16
- {scdata-1.0.0 → scdata-1.0.2}/scdata/device/device.py +2 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/io/device_file.py +1 -1
- {scdata-1.0.0 → scdata-1.0.2}/scdata/models/models.py +1 -1
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/plot/plot_tools.py +37 -16
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/plot/ts_dispersion_uplot.py +1 -1
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/plot/ts_uplot.py +1 -1
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/test.py +27 -16
- scdata-1.0.2/scdata/test/tools/combine.py +64 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/units.py +1 -1
- {scdata-1.0.0 → scdata-1.0.2/scdata.egg-info}/PKG-INFO +5 -7
- {scdata-1.0.0 → scdata-1.0.2}/scdata.egg-info/requires.txt +1 -1
- {scdata-1.0.0 → scdata-1.0.2}/setup.py +1 -1
- scdata-1.0.0/scdata/test/tools/combine.py +0 -60
- {scdata-1.0.0 → scdata-1.0.2}/LICENSE +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/MANIFEST.in +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/_config/__init__.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/_config/custom_logger.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/device/__init__.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/device/process/__init__.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/device/process/alphasense.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/device/process/baseline.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/device/process/formulae.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/device/process/geoseries.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/device/process/params.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/device/process/regression.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/device/process/timeseries.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/io/__init__.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/io/device_api.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/io/model.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/models/__init__.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/__init__.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/checks/__init__.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/checks/checks.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/dispersion/__init__.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/dispersion/dispersion.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/export/__init__.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/export/templates/sc_template.html +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/export/to_file.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/plot/__init__.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/plot/box_plot.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/plot/heatmap_iplot.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/plot/heatmap_plot.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/plot/maps.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/plot/scatter_dispersion_grid.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/plot/scatter_iplot.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/plot/scatter_plot.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/plot/target_diagram.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/plot/ts_dendrogram.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/plot/ts_dispersion_grid.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/plot/ts_dispersion_plot.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/plot/ts_iplot.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/plot/ts_plot.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/plot/ts_scatter.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/tools/__init__.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/tools/history.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/test/tools/prepare.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/__init__.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/cleaning.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/custom_logger.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/date.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/dictmerge.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/find.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/gets.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/interim/example.csv +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/interim/geodata.csv +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/lazy.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/location.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/report.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/stats.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/uploads/example_upload_1.json +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/uploads/example_zenodo_upload.yaml +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/uploads/report.pdf +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/url_check.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/zenodo.py +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/zenodo_templates/README.md +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/zenodo_templates/template_zenodo_dataset.json +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata/tools/zenodo_templates/template_zenodo_publication.json +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata.egg-info/SOURCES.txt +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata.egg-info/dependency_links.txt +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata.egg-info/not-zip-safe +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/scdata.egg-info/top_level.txt +0 -0
- {scdata-1.0.0 → scdata-1.0.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: scdata
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Analysis of sensors and time series data
|
|
5
5
|
Home-page: https://github.com/fablabbcn/smartcitizen-data
|
|
6
6
|
Author: oscgonfer
|
|
@@ -29,7 +29,7 @@ Requires-Dist: numpy~=1.25.2
|
|
|
29
29
|
Requires-Dist: pandas~=2.2.2
|
|
30
30
|
Requires-Dist: pydantic
|
|
31
31
|
Requires-Dist: pytest
|
|
32
|
-
Requires-Dist: PyYAML
|
|
32
|
+
Requires-Dist: PyYAML~=6.0.1
|
|
33
33
|
Requires-Dist: requests
|
|
34
34
|
Requires-Dist: scipy
|
|
35
35
|
Requires-Dist: scikit-learn
|
|
@@ -46,21 +46,19 @@ Smart Citizen Data
|
|
|
46
46
|
[](https://zenodo.org/badge/latestdoi/97752018)
|
|
47
47
|
[](https://mybinder.org/v2/gh/fablabbcn/smartcitizen-data-framework/master?filepath=%2Fexamples%2Fnotebooks)
|
|
48
48
|
[](https://badge.fury.io/py/scdata)
|
|
49
|
-
[](https://github.com/fablabbcn/smartcitizen-data/actions/workflows/python-multiple-versions.yml)
|
|
50
50
|
|
|
51
51
|
Welcome to **SmartCitizen Data**. This is a data analysis framework for working with sensor data in different ways:
|
|
52
52
|
|
|
53
53
|
- Interacting with several sensors APIs
|
|
54
54
|
- Clean data, export and calculate metrics
|
|
55
55
|
- Model sensor data and calibrate sensors
|
|
56
|
-
- Generate data visualisations - matplotlib, [plotly](https://plotly.com/) or [uplot](https://leeoniya.github.io/uPlot)
|
|
56
|
+
- Generate data visualisations - matplotlib, ~[plotly](https://plotly.com/)~ or [uplot](https://leeoniya.github.io/uPlot)
|
|
57
57
|
- Generate analysis reports in html or pdf and upload them to [Zenodo](http://zenodo.org)
|
|
58
58
|
|
|
59
|
-
A full documentation of the framework is detailed in [the Smart Citizen Docs](https://docs.smartcitizen.me/Data/Data%20Analysis/).
|
|
60
|
-
|
|
61
59
|
## Installation
|
|
62
60
|
|
|
63
|
-
You can check it out in the [](https://mybinder.org/v2/gh/fablabbcn/smartcitizen-data-framework/master?filepath=%2Fexamples%2Fnotebooks) before installing if you want. Works with `Python 3.*` (tested
|
|
61
|
+
You can check it out in the [](https://mybinder.org/v2/gh/fablabbcn/smartcitizen-data-framework/master?filepath=%2Fexamples%2Fnotebooks) before installing if you want. Works with `Python 3.*` (tested between 3.9 and 3.11).
|
|
64
62
|
|
|
65
63
|
You can just run:
|
|
66
64
|
|
|
@@ -4,21 +4,19 @@ Smart Citizen Data
|
|
|
4
4
|
[](https://zenodo.org/badge/latestdoi/97752018)
|
|
5
5
|
[](https://mybinder.org/v2/gh/fablabbcn/smartcitizen-data-framework/master?filepath=%2Fexamples%2Fnotebooks)
|
|
6
6
|
[](https://badge.fury.io/py/scdata)
|
|
7
|
-
[](https://github.com/fablabbcn/smartcitizen-data/actions/workflows/python-multiple-versions.yml)
|
|
8
8
|
|
|
9
9
|
Welcome to **SmartCitizen Data**. This is a data analysis framework for working with sensor data in different ways:
|
|
10
10
|
|
|
11
11
|
- Interacting with several sensors APIs
|
|
12
12
|
- Clean data, export and calculate metrics
|
|
13
13
|
- Model sensor data and calibrate sensors
|
|
14
|
-
- Generate data visualisations - matplotlib, [plotly](https://plotly.com/) or [uplot](https://leeoniya.github.io/uPlot)
|
|
14
|
+
- Generate data visualisations - matplotlib, ~[plotly](https://plotly.com/)~ or [uplot](https://leeoniya.github.io/uPlot)
|
|
15
15
|
- Generate analysis reports in html or pdf and upload them to [Zenodo](http://zenodo.org)
|
|
16
16
|
|
|
17
|
-
A full documentation of the framework is detailed in [the Smart Citizen Docs](https://docs.smartcitizen.me/Data/Data%20Analysis/).
|
|
18
|
-
|
|
19
17
|
## Installation
|
|
20
18
|
|
|
21
|
-
You can check it out in the [](https://mybinder.org/v2/gh/fablabbcn/smartcitizen-data-framework/master?filepath=%2Fexamples%2Fnotebooks) before installing if you want. Works with `Python 3.*` (tested
|
|
19
|
+
You can check it out in the [](https://mybinder.org/v2/gh/fablabbcn/smartcitizen-data-framework/master?filepath=%2Fexamples%2Fnotebooks) before installing if you want. Works with `Python 3.*` (tested between 3.9 and 3.11).
|
|
22
20
|
|
|
23
21
|
You can just run:
|
|
24
22
|
|
|
@@ -38,9 +38,6 @@ class Config(object):
|
|
|
38
38
|
if 'IPython' in sys.modules: _ipython_avail = True
|
|
39
39
|
else: _ipython_avail = False
|
|
40
40
|
|
|
41
|
-
# Returns when iterables cannot be fully processed
|
|
42
|
-
_strict = False
|
|
43
|
-
|
|
44
41
|
# Timeout for http requests
|
|
45
42
|
_timeout = 3
|
|
46
43
|
_max_http_retries = 2
|
|
@@ -51,20 +48,9 @@ class Config(object):
|
|
|
51
48
|
### ---------------------------------------
|
|
52
49
|
### -----------------DATA------------------
|
|
53
50
|
### ---------------------------------------
|
|
54
|
-
|
|
55
51
|
data = {
|
|
56
|
-
|
|
57
|
-
'reload_metadata': True
|
|
58
|
-
# Whether or not to load or store cached data (saves time when requesting a lot of data)
|
|
59
|
-
'load_cached_api': True,
|
|
60
|
-
'store_cached_api': True,
|
|
61
|
-
# If reloading data from the API, how much gap between the saved data and the
|
|
62
|
-
# latest reading in the API should be ignore
|
|
63
|
-
'cached_data_margin': 1,
|
|
64
|
-
# clean_na
|
|
65
|
-
'clean_na': None,
|
|
66
|
-
# Ignore additional channels from API or CSV that are not in the blueprint.json
|
|
67
|
-
'strict_load': False
|
|
52
|
+
'cached_data_margin': '10Min', # Data Margin in minutes for consecutive requests
|
|
53
|
+
'reload_metadata': True # Reload metadata
|
|
68
54
|
}
|
|
69
55
|
|
|
70
56
|
# Maximum amount of points to load when postprocessing data
|
|
@@ -300,6 +300,7 @@ class Device(BaseModel):
|
|
|
300
300
|
else:
|
|
301
301
|
raise NotImplementedError(f'Cache needs to be a .csv file. Got {cache}.')
|
|
302
302
|
|
|
303
|
+
# Make request with a logical min_date
|
|
303
304
|
if not cached_data.empty:
|
|
304
305
|
# Update min_date
|
|
305
306
|
min_date=cached_data.index[-1].tz_convert('UTC')+Timedelta(frequency)
|
|
@@ -327,6 +328,7 @@ class Device(BaseModel):
|
|
|
327
328
|
# In principle this links both dataframes as they are unmutable
|
|
328
329
|
self.data = self.handler.data
|
|
329
330
|
# Wrap it all up
|
|
331
|
+
# TODO Avoid doing this if not needed?
|
|
330
332
|
self.loaded = self.__load_wrapup__(max_amount, convert_units=convert_units, convert_names=convert_names, cached_data=cached_data)
|
|
331
333
|
|
|
332
334
|
self.processed = False
|
|
@@ -128,7 +128,7 @@ def read_csv_file(path, timezone, frequency=None, clean_na=None, index_name='',
|
|
|
128
128
|
|
|
129
129
|
# Read pandas dataframe
|
|
130
130
|
|
|
131
|
-
df = read_csv(path,
|
|
131
|
+
df = read_csv(path, skiprows=skiprows, sep=sep,
|
|
132
132
|
encoding=encoding, encoding_errors='ignore')
|
|
133
133
|
|
|
134
134
|
flag_found = False
|
|
@@ -93,14 +93,15 @@ def prepare_data(test, traces, options):
|
|
|
93
93
|
logger.warning(f'The trace {traces[trace]} was not placed in any subplot. Assuming subplot #1')
|
|
94
94
|
traces[trace]['subplot'] = 1
|
|
95
95
|
|
|
96
|
+
|
|
96
97
|
ndevs = traces[trace]['devices']
|
|
97
98
|
nchans = traces[trace]['channel']
|
|
98
99
|
|
|
99
100
|
# Make them lists always
|
|
100
101
|
if ndevs == 'all': devices = [device.id for device in test.devices]
|
|
102
|
+
## TODO Make it regex compatible!
|
|
101
103
|
elif type(ndevs) == str or type(ndevs) == int: devices = [ndevs]
|
|
102
104
|
else: devices = ndevs
|
|
103
|
-
print (devices)
|
|
104
105
|
|
|
105
106
|
for ndev in devices:
|
|
106
107
|
|
|
@@ -158,8 +159,23 @@ def prepare_data(test, traces, options):
|
|
|
158
159
|
# Remove column for filtering from dfdev
|
|
159
160
|
dfdev.drop(columns=[col_name], inplace = True)
|
|
160
161
|
|
|
162
|
+
# Resample it
|
|
163
|
+
if options['frequency'] is not None:
|
|
164
|
+
logger.info(f"Resampling at {options['frequency']}")
|
|
165
|
+
|
|
166
|
+
if 'resample' in options:
|
|
167
|
+
|
|
168
|
+
if options['resample'] == 'max': dfdev = dfdev.resample(options['frequency']).max()
|
|
169
|
+
if options['resample'] == 'min': dfdev = dfdev.resample(options['frequency']).min()
|
|
170
|
+
if options['resample'] == 'mean': dfdev = dfdev.resample(options['frequency']).mean()
|
|
171
|
+
|
|
172
|
+
else:
|
|
173
|
+
dfdev = dfdev.resample(options['frequency']).mean()
|
|
174
|
+
|
|
161
175
|
# Combine it in the df
|
|
162
176
|
df = df.combine_first(dfdev)
|
|
177
|
+
|
|
178
|
+
## TODO Is this working?
|
|
163
179
|
# Add average or other extras
|
|
164
180
|
# TODO Check this to simplify
|
|
165
181
|
# https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.core.resample.Resampler.aggregate.html
|
|
@@ -168,15 +184,19 @@ def prepare_data(test, traces, options):
|
|
|
168
184
|
|
|
169
185
|
nextras = list()
|
|
170
186
|
for device in traces[trace]['devices']:
|
|
171
|
-
|
|
172
|
-
|
|
187
|
+
if type(traces[trace]['channel']) == 'list':
|
|
188
|
+
for channel in traces[trace]['channel']:
|
|
189
|
+
nextras.append(f'{channel}_{device}')
|
|
190
|
+
else:
|
|
191
|
+
nextras.append(f"{traces[trace]['channel']}_{device}")
|
|
173
192
|
|
|
174
193
|
if extra == 'bands':
|
|
175
|
-
ubn = channel + f"-{trace}-
|
|
176
|
-
lbn = channel + f"-{trace}-
|
|
194
|
+
ubn = channel + f"-{trace}-UPPER-BAND"
|
|
195
|
+
lbn = channel + f"-{trace}-LOWER-BAND"
|
|
177
196
|
|
|
178
|
-
|
|
179
|
-
df[
|
|
197
|
+
logger.info('Using 3sig bands')
|
|
198
|
+
df[ubn] = df.loc[:, nextras].mean(axis = 1) + 3*df.loc[:, nextras].std(axis = 1)
|
|
199
|
+
df[lbn] = df.loc[:, nextras].mean(axis = 1) - 3*df.loc[:, nextras].std(axis = 1)
|
|
180
200
|
|
|
181
201
|
subplots[traces[trace]['subplot']-1].append(ubn)
|
|
182
202
|
subplots[traces[trace]['subplot']-1].append(lbn)
|
|
@@ -208,18 +228,19 @@ def prepare_data(test, traces, options):
|
|
|
208
228
|
if df.empty:
|
|
209
229
|
logger.error('Empty dataframe for plot')
|
|
210
230
|
return None, None
|
|
211
|
-
# Resample it
|
|
212
|
-
if options['frequency'] is not None:
|
|
213
|
-
logger.info(f"Resampling at {options['frequency']}")
|
|
214
231
|
|
|
215
|
-
|
|
232
|
+
# # Resample it
|
|
233
|
+
# if options['frequency'] is not None:
|
|
234
|
+
# logger.info(f"Resampling at {options['frequency']}")
|
|
216
235
|
|
|
217
|
-
|
|
218
|
-
if options['resample'] == 'min': df = df.resample(options['frequency']).min()
|
|
219
|
-
if options['resample'] == 'mean': df = df.resample(options['frequency']).mean()
|
|
236
|
+
# if 'resample' in options:
|
|
220
237
|
|
|
221
|
-
|
|
222
|
-
|
|
238
|
+
# if options['resample'] == 'max': df = df.resample(options['frequency']).max()
|
|
239
|
+
# if options['resample'] == 'min': df = df.resample(options['frequency']).min()
|
|
240
|
+
# if options['resample'] == 'mean': df = df.resample(options['frequency']).mean()
|
|
241
|
+
|
|
242
|
+
# else:
|
|
243
|
+
# df = df.resample(options['frequency']).mean()
|
|
223
244
|
|
|
224
245
|
# Clean na
|
|
225
246
|
if options['clean_na'] is not None:
|
|
@@ -149,7 +149,7 @@ def ts_dispersion_uplot(self, **kwargs):
|
|
|
149
149
|
|
|
150
150
|
if formatting['join_sbplot']: n_subplots = 1
|
|
151
151
|
else: n_subplots = 2
|
|
152
|
-
udf.index = udf.index.astype(
|
|
152
|
+
udf.index = udf.index.astype('int64')/10**9
|
|
153
153
|
|
|
154
154
|
# Compose subplots lists
|
|
155
155
|
for device in self.devices:
|
|
@@ -67,7 +67,6 @@ class Test(BaseModel):
|
|
|
67
67
|
|
|
68
68
|
self.devices = TypeAdapter(List[Device]).validate_python(tj['devices'])
|
|
69
69
|
self.options = TypeAdapter(TestOptions).validate_python(tj['options'])
|
|
70
|
-
print (tj['meta'])
|
|
71
70
|
self.type = tj['meta']['type']
|
|
72
71
|
if self.name != tj['meta']['name']:
|
|
73
72
|
raise ValueError('Name not matching')
|
|
@@ -334,20 +333,20 @@ class Test(BaseModel):
|
|
|
334
333
|
|
|
335
334
|
return fname
|
|
336
335
|
|
|
337
|
-
def cache(self):
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
336
|
+
# def cache(self):
|
|
337
|
+
# logger.info(f'Caching files...')
|
|
338
|
+
# for device in self.devices:
|
|
339
|
+
# logger.info(f'Caching files for {device.id}...')
|
|
341
340
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
341
|
+
# cached_file_path = join(self.path, 'cached')
|
|
342
|
+
# if not exists(cached_file_path):
|
|
343
|
+
# logger.info('Creating path for exporting cached data')
|
|
344
|
+
# makedirs(cached_file_path)
|
|
346
345
|
|
|
347
|
-
|
|
348
|
-
|
|
346
|
+
# if device.export(cached_file_path, forced_overwrite = True, file_format = 'csv'):
|
|
347
|
+
# logger.info(f'Device {device.id} cached')
|
|
349
348
|
|
|
350
|
-
|
|
349
|
+
# return all([exists(join(self.path, 'cached', f'{d.id}.csv')) for d in self.devices])
|
|
351
350
|
|
|
352
351
|
async def load(self):
|
|
353
352
|
'''
|
|
@@ -359,17 +358,29 @@ class Test(BaseModel):
|
|
|
359
358
|
'''
|
|
360
359
|
logger.info('Loading test...')
|
|
361
360
|
|
|
361
|
+
if self.options.cache:
|
|
362
|
+
cache_dir = join(self.path, 'cached')
|
|
363
|
+
if not exists(cache_dir):
|
|
364
|
+
logger.info('Creating path for exporting cached data...')
|
|
365
|
+
makedirs(cache_dir)
|
|
366
|
+
logger.info(f'Cache will be available in: {cache_dir}')
|
|
367
|
+
|
|
362
368
|
for device in self.devices:
|
|
363
369
|
# Check for cached data
|
|
364
|
-
|
|
370
|
+
device_cache_path = ''
|
|
365
371
|
if self.options.cache:
|
|
366
372
|
tentative_path = join(self.path, 'cached', f'{device.id}.csv')
|
|
367
|
-
if exists(tentative_path):
|
|
373
|
+
if exists(tentative_path):
|
|
374
|
+
device_cache_path = tentative_path
|
|
375
|
+
|
|
368
376
|
# Load device (no need to go async, it's fast enough)
|
|
369
|
-
await device.load(cache=
|
|
377
|
+
await device.load(cache=device_cache_path)
|
|
378
|
+
|
|
379
|
+
if self.options.cache:
|
|
380
|
+
if device.export(cache_dir, forced_overwrite = True, file_format = 'csv'):
|
|
381
|
+
logger.info(f'Device {device.id} cached')
|
|
370
382
|
|
|
371
383
|
logger.info('Test load done')
|
|
372
|
-
if self.options.cache: self.cache()
|
|
373
384
|
|
|
374
385
|
self.loaded = all([d.loaded for d in self.devices])
|
|
375
386
|
return self.loaded
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from pandas import DataFrame
|
|
2
|
+
from scdata.tools.custom_logger import logger
|
|
3
|
+
from scdata.device import Device
|
|
4
|
+
|
|
5
|
+
def combine(self, devices = None, channels = None, resample = True, frequency = '1Min'):
|
|
6
|
+
"""
|
|
7
|
+
Combines devices from a test into a new dataframe, following the
|
|
8
|
+
naming as follows: DEVICE-NAME_READING-NAME
|
|
9
|
+
Parameters
|
|
10
|
+
----------
|
|
11
|
+
devices: list or None
|
|
12
|
+
None
|
|
13
|
+
If None, includes all the devices in self.devices
|
|
14
|
+
channels: list or None
|
|
15
|
+
None
|
|
16
|
+
If None, includes all the readings in self.readings
|
|
17
|
+
Returns
|
|
18
|
+
-------
|
|
19
|
+
Dataframe if successful or False otherwise
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
dfc = DataFrame()
|
|
23
|
+
if not self.loaded:
|
|
24
|
+
logger.error('Cannot combine data if test is not loaded. Maybe test.load() first?')
|
|
25
|
+
|
|
26
|
+
if devices is None:
|
|
27
|
+
dl = [device.id for device in self.devices]
|
|
28
|
+
else:
|
|
29
|
+
# Only requested AND available
|
|
30
|
+
dl = list(set(devices).intersection([device.id for device in self.devices]))
|
|
31
|
+
if len(dl) != len(devices):
|
|
32
|
+
logger.warning('Requested devices are not all present in devices')
|
|
33
|
+
logger.info(f'Discarding {set(devices).difference([device.id for device in self.devices])}')
|
|
34
|
+
|
|
35
|
+
for device in dl:
|
|
36
|
+
new_names = list()
|
|
37
|
+
|
|
38
|
+
if channels is None:
|
|
39
|
+
channel_list = list(self.get_device(device).data.columns)
|
|
40
|
+
else:
|
|
41
|
+
# Only pick the ones that are actually present
|
|
42
|
+
channel_list = list(set(channels).intersection(list(self.get_device(device).data.columns)))
|
|
43
|
+
|
|
44
|
+
if any([channel not in channel_list for channel in channels]):
|
|
45
|
+
logger.warning(f'Requested channels are not all present in readings for device {device}')
|
|
46
|
+
logger.warning(f'Discarding {list(set(channels).difference(list(self.get_device(device).data.columns)))}')
|
|
47
|
+
|
|
48
|
+
rename = dict()
|
|
49
|
+
|
|
50
|
+
for channel in channel_list:
|
|
51
|
+
rename[channel] = f'{channel}_{self.get_device(device).id}'
|
|
52
|
+
|
|
53
|
+
df = self.get_device(device).data[channel_list].copy()
|
|
54
|
+
df.rename(columns = rename, inplace = True)
|
|
55
|
+
if resample:
|
|
56
|
+
df = df.resample(frequency).mean()
|
|
57
|
+
dfc = dfc.combine_first(df)
|
|
58
|
+
|
|
59
|
+
if dfc.empty:
|
|
60
|
+
logger.error('Error ocurred while combining data. Review data')
|
|
61
|
+
return False
|
|
62
|
+
else:
|
|
63
|
+
logger.info('Data combined successfully')
|
|
64
|
+
return dfc
|
|
@@ -92,7 +92,7 @@ def get_units_convf(sensor, from_units):
|
|
|
92
92
|
else: molecular_weight = 1
|
|
93
93
|
|
|
94
94
|
# Check if channel is in look-up table
|
|
95
|
-
if channel_lut[channel] != from_units and from_units != "":
|
|
95
|
+
if channel_lut[channel] != from_units and from_units != "" and from_units is not None:
|
|
96
96
|
logger.info(f"Converting units for {sensor}. From {from_units} to {channel_lut[channel]}")
|
|
97
97
|
for unit in unit_convertion_lut:
|
|
98
98
|
# Get units
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: scdata
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Analysis of sensors and time series data
|
|
5
5
|
Home-page: https://github.com/fablabbcn/smartcitizen-data
|
|
6
6
|
Author: oscgonfer
|
|
@@ -29,7 +29,7 @@ Requires-Dist: numpy~=1.25.2
|
|
|
29
29
|
Requires-Dist: pandas~=2.2.2
|
|
30
30
|
Requires-Dist: pydantic
|
|
31
31
|
Requires-Dist: pytest
|
|
32
|
-
Requires-Dist: PyYAML
|
|
32
|
+
Requires-Dist: PyYAML~=6.0.1
|
|
33
33
|
Requires-Dist: requests
|
|
34
34
|
Requires-Dist: scipy
|
|
35
35
|
Requires-Dist: scikit-learn
|
|
@@ -46,21 +46,19 @@ Smart Citizen Data
|
|
|
46
46
|
[](https://zenodo.org/badge/latestdoi/97752018)
|
|
47
47
|
[](https://mybinder.org/v2/gh/fablabbcn/smartcitizen-data-framework/master?filepath=%2Fexamples%2Fnotebooks)
|
|
48
48
|
[](https://badge.fury.io/py/scdata)
|
|
49
|
-
[](https://github.com/fablabbcn/smartcitizen-data/actions/workflows/python-multiple-versions.yml)
|
|
50
50
|
|
|
51
51
|
Welcome to **SmartCitizen Data**. This is a data analysis framework for working with sensor data in different ways:
|
|
52
52
|
|
|
53
53
|
- Interacting with several sensors APIs
|
|
54
54
|
- Clean data, export and calculate metrics
|
|
55
55
|
- Model sensor data and calibrate sensors
|
|
56
|
-
- Generate data visualisations - matplotlib, [plotly](https://plotly.com/) or [uplot](https://leeoniya.github.io/uPlot)
|
|
56
|
+
- Generate data visualisations - matplotlib, ~[plotly](https://plotly.com/)~ or [uplot](https://leeoniya.github.io/uPlot)
|
|
57
57
|
- Generate analysis reports in html or pdf and upload them to [Zenodo](http://zenodo.org)
|
|
58
58
|
|
|
59
|
-
A full documentation of the framework is detailed in [the Smart Citizen Docs](https://docs.smartcitizen.me/Data/Data%20Analysis/).
|
|
60
|
-
|
|
61
59
|
## Installation
|
|
62
60
|
|
|
63
|
-
You can check it out in the [](https://mybinder.org/v2/gh/fablabbcn/smartcitizen-data-framework/master?filepath=%2Fexamples%2Fnotebooks) before installing if you want. Works with `Python 3.*` (tested
|
|
61
|
+
You can check it out in the [](https://mybinder.org/v2/gh/fablabbcn/smartcitizen-data-framework/master?filepath=%2Fexamples%2Fnotebooks) before installing if you want. Works with `Python 3.*` (tested between 3.9 and 3.11).
|
|
64
62
|
|
|
65
63
|
You can just run:
|
|
66
64
|
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
from pandas import DataFrame
|
|
2
|
-
from scdata.tools.custom_logger import logger
|
|
3
|
-
from scdata.device import Device
|
|
4
|
-
|
|
5
|
-
def combine(self, devices = None, readings = None):
|
|
6
|
-
"""
|
|
7
|
-
Combines devices from a test into a new dataframe, following the
|
|
8
|
-
naming as follows: DEVICE-NAME_READING-NAME
|
|
9
|
-
Parameters
|
|
10
|
-
----------
|
|
11
|
-
devices: list or None
|
|
12
|
-
None
|
|
13
|
-
If None, includes all the devices in self.devices
|
|
14
|
-
readings: list or None
|
|
15
|
-
None
|
|
16
|
-
If None, includes all the readings in self.readings
|
|
17
|
-
Returns
|
|
18
|
-
-------
|
|
19
|
-
Dataframe if successful or False otherwise
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
dfc = DataFrame()
|
|
23
|
-
|
|
24
|
-
if devices is None:
|
|
25
|
-
dl = list(self.devices.keys())
|
|
26
|
-
else:
|
|
27
|
-
# Only pick the ones that are actually present
|
|
28
|
-
dl = list(set(devices).intersection(list(self.devices.keys())))
|
|
29
|
-
if len(dl) != len(devices):
|
|
30
|
-
logger.warning('Requested devices are not all present in devices')
|
|
31
|
-
logger.info(f'Discarding {set(devices).difference(list(self.devices.keys()))}')
|
|
32
|
-
|
|
33
|
-
for device in dl:
|
|
34
|
-
new_names = list()
|
|
35
|
-
|
|
36
|
-
if readings is None:
|
|
37
|
-
rl = list(self.devices[device].readings.columns)
|
|
38
|
-
else:
|
|
39
|
-
# Only pick the ones that are actually present
|
|
40
|
-
rl = list(set(readings).intersection(list(self.devices[device].readings.columns)))
|
|
41
|
-
|
|
42
|
-
if any([reading not in rl for reading in readings]):
|
|
43
|
-
logger.warning(f'Requested readings are not all present in readings for device {device}')
|
|
44
|
-
logger.warning(f'Discarding {list(set(readings).difference(list(self.devices[device].readings.columns)))}')
|
|
45
|
-
|
|
46
|
-
rename = dict()
|
|
47
|
-
|
|
48
|
-
for reading in rl:
|
|
49
|
-
rename[reading] = reading + '_' + self.devices[device].id
|
|
50
|
-
|
|
51
|
-
df = self.devices[device].readings[rl].copy()
|
|
52
|
-
df.rename(columns = rename, inplace = True)
|
|
53
|
-
dfc = dfc.combine_first(df)
|
|
54
|
-
|
|
55
|
-
if dfc.empty:
|
|
56
|
-
logger.error('Error ocurred while combining data. Review data')
|
|
57
|
-
return False
|
|
58
|
-
else:
|
|
59
|
-
logger.info('Data combined successfully')
|
|
60
|
-
return dfc
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scdata-1.0.0 → scdata-1.0.2}/scdata/tools/zenodo_templates/template_zenodo_publication.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|