boris-behav-obs 9.7.7__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 boris-behav-obs might be problematic. Click here for more details.
- boris/__init__.py +26 -0
- boris/__main__.py +25 -0
- boris/about.py +143 -0
- boris/add_modifier.py +635 -0
- boris/add_modifier_ui.py +303 -0
- boris/advanced_event_filtering.py +455 -0
- boris/analysis_plugins/__init__.py +0 -0
- boris/analysis_plugins/_latency.py +59 -0
- boris/analysis_plugins/irr_cohen_kappa.py +109 -0
- boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
- boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
- boris/analysis_plugins/number_of_occurences.py +22 -0
- boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
- boris/analysis_plugins/time_budget.py +61 -0
- boris/behav_coding_map_creator.py +1110 -0
- boris/behavior_binary_table.py +305 -0
- boris/behaviors_coding_map.py +239 -0
- boris/boris_cli.py +340 -0
- boris/cmd_arguments.py +49 -0
- boris/coding_pad.py +280 -0
- boris/config.py +785 -0
- boris/config_file.py +356 -0
- boris/connections.py +409 -0
- boris/converters.py +333 -0
- boris/converters_ui.py +225 -0
- boris/cooccurence.py +250 -0
- boris/core.py +5901 -0
- boris/core_qrc.py +15958 -0
- boris/core_ui.py +1107 -0
- boris/db_functions.py +324 -0
- boris/dev.py +134 -0
- boris/dialog.py +1108 -0
- boris/duration_widget.py +238 -0
- boris/edit_event.py +245 -0
- boris/edit_event_ui.py +233 -0
- boris/event_operations.py +1040 -0
- boris/events_cursor.py +61 -0
- boris/events_snapshots.py +596 -0
- boris/exclusion_matrix.py +141 -0
- boris/export_events.py +1006 -0
- boris/export_observation.py +1203 -0
- boris/external_processes.py +332 -0
- boris/geometric_measurement.py +941 -0
- boris/gui_utilities.py +135 -0
- boris/image_overlay.py +72 -0
- boris/import_observations.py +242 -0
- boris/ipc_mpv.py +325 -0
- boris/irr.py +634 -0
- boris/latency.py +244 -0
- boris/measurement_widget.py +161 -0
- boris/media_file.py +115 -0
- boris/menu_options.py +213 -0
- boris/modifier_coding_map_creator.py +1013 -0
- boris/modifiers_coding_map.py +157 -0
- boris/mpv.py +2016 -0
- boris/mpv2.py +2193 -0
- boris/observation.py +1453 -0
- boris/observation_operations.py +2538 -0
- boris/observation_ui.py +679 -0
- boris/observations_list.py +337 -0
- boris/otx_parser.py +442 -0
- boris/param_panel.py +201 -0
- boris/param_panel_ui.py +305 -0
- boris/player_dock_widget.py +198 -0
- boris/plot_data_module.py +536 -0
- boris/plot_events.py +634 -0
- boris/plot_events_rt.py +237 -0
- boris/plot_spectrogram_rt.py +316 -0
- boris/plot_waveform_rt.py +230 -0
- boris/plugins.py +431 -0
- boris/portion/__init__.py +31 -0
- boris/portion/const.py +95 -0
- boris/portion/dict.py +365 -0
- boris/portion/func.py +52 -0
- boris/portion/interval.py +581 -0
- boris/portion/io.py +181 -0
- boris/preferences.py +510 -0
- boris/preferences_ui.py +770 -0
- boris/project.py +2007 -0
- boris/project_functions.py +2041 -0
- boris/project_import_export.py +1096 -0
- boris/project_ui.py +794 -0
- boris/qrc_boris.py +10389 -0
- boris/qrc_boris5.py +2579 -0
- boris/select_modifiers.py +312 -0
- boris/select_observations.py +210 -0
- boris/select_subj_behav.py +286 -0
- boris/state_events.py +197 -0
- boris/subjects_pad.py +106 -0
- boris/synthetic_time_budget.py +290 -0
- boris/time_budget_functions.py +1136 -0
- boris/time_budget_widget.py +1039 -0
- boris/transitions.py +365 -0
- boris/utilities.py +1810 -0
- boris/version.py +24 -0
- boris/video_equalizer.py +159 -0
- boris/video_equalizer_ui.py +248 -0
- boris/video_operations.py +310 -0
- boris/view_df.py +104 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +538 -0
- boris_behav_obs-9.7.7.dist-info/METADATA +139 -0
- boris_behav_obs-9.7.7.dist-info/RECORD +109 -0
- boris_behav_obs-9.7.7.dist-info/WHEEL +5 -0
- boris_behav_obs-9.7.7.dist-info/entry_points.txt +2 -0
- boris_behav_obs-9.7.7.dist-info/licenses/LICENSE.TXT +674 -0
- boris_behav_obs-9.7.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BORIS
|
|
3
|
+
Behavioral Observation Research Interactive Software
|
|
4
|
+
Copyright 2012-2025 Olivier Friard
|
|
5
|
+
|
|
6
|
+
This program is free software; you can redistribute it and/or modify
|
|
7
|
+
it under the terms of the GNU General Public License as published by
|
|
8
|
+
the Free Software Foundation; either version 2 of the License, or
|
|
9
|
+
(at your option) any later version.
|
|
10
|
+
|
|
11
|
+
This program is distributed in the hope that it will be useful,
|
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
GNU General Public License for more details.
|
|
15
|
+
|
|
16
|
+
You should have received a copy of the GNU General Public License
|
|
17
|
+
along with this program; if not, write to the Free Software
|
|
18
|
+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
19
|
+
MA 02110-1301, USA.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import math
|
|
23
|
+
import statistics
|
|
24
|
+
from decimal import Decimal as dec
|
|
25
|
+
from typing import Tuple
|
|
26
|
+
import tablib
|
|
27
|
+
import logging
|
|
28
|
+
import itertools
|
|
29
|
+
import re
|
|
30
|
+
|
|
31
|
+
from . import config as cfg
|
|
32
|
+
from . import db_functions
|
|
33
|
+
from . import portion as I
|
|
34
|
+
from . import project_functions
|
|
35
|
+
from . import observation_operations
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def default_value(ethogram: dict, behavior_code: str, param):
|
|
39
|
+
"""
|
|
40
|
+
return value for duration in case of point event
|
|
41
|
+
"""
|
|
42
|
+
default_value_ = 0.0
|
|
43
|
+
behavior_type = project_functions.event_type(behavior_code, ethogram)
|
|
44
|
+
|
|
45
|
+
if behavior_type in cfg.POINT_EVENT_TYPES and param in (
|
|
46
|
+
"duration",
|
|
47
|
+
"duration mean",
|
|
48
|
+
"duration stdev",
|
|
49
|
+
"proportion of time",
|
|
50
|
+
):
|
|
51
|
+
default_value_ = cfg.NA
|
|
52
|
+
|
|
53
|
+
if behavior_type in cfg.STATE_EVENT_TYPES and param in (
|
|
54
|
+
"duration mean",
|
|
55
|
+
"duration stdev",
|
|
56
|
+
):
|
|
57
|
+
default_value_ = cfg.NA
|
|
58
|
+
|
|
59
|
+
return default_value_
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def init_behav_modif(ethogram: dict, selected_subjects: list, distinct_behav_modif: list, parameters: dict) -> dict:
|
|
63
|
+
"""
|
|
64
|
+
initialize dictionary with subject, behaviors and modifiers
|
|
65
|
+
"""
|
|
66
|
+
behaviors: dict = {}
|
|
67
|
+
for subj in selected_subjects:
|
|
68
|
+
behaviors[subj] = {}
|
|
69
|
+
for behav_modif in distinct_behav_modif:
|
|
70
|
+
behav, modif = behav_modif
|
|
71
|
+
behav_modif_str = "|".join(behav_modif) if modif else behav
|
|
72
|
+
|
|
73
|
+
if behav_modif_str not in behaviors[subj]:
|
|
74
|
+
behaviors[subj][behav_modif_str] = {}
|
|
75
|
+
|
|
76
|
+
for param in parameters:
|
|
77
|
+
behaviors[subj][behav_modif_str][param[0]] = default_value(ethogram, behav, param[0])
|
|
78
|
+
|
|
79
|
+
return behaviors
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def init_behav_modif_bin(ethogram: dict, selected_subjects: list, distinct_behav_modif: list, parameters: dict) -> dict:
|
|
83
|
+
"""
|
|
84
|
+
initialize dictionary with subject, behaviors and modifiers
|
|
85
|
+
"""
|
|
86
|
+
behaviors: dict = {}
|
|
87
|
+
for subj in selected_subjects:
|
|
88
|
+
behaviors[subj] = {}
|
|
89
|
+
for behav_modif in distinct_behav_modif:
|
|
90
|
+
if behav_modif not in behaviors[subj]:
|
|
91
|
+
behaviors[subj][behav_modif] = {}
|
|
92
|
+
|
|
93
|
+
for param in parameters:
|
|
94
|
+
behaviors[subj][behav_modif][param[0]] = default_value(ethogram, behav_modif[0], param[0])
|
|
95
|
+
|
|
96
|
+
return behaviors
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class StdevFunc:
|
|
100
|
+
"""
|
|
101
|
+
class to enable std dev function in SQL
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(self):
|
|
105
|
+
self.M = 0.0
|
|
106
|
+
self.S = 0.0
|
|
107
|
+
self.k = 1
|
|
108
|
+
|
|
109
|
+
def step(self, value):
|
|
110
|
+
if value is None:
|
|
111
|
+
return
|
|
112
|
+
tM = self.M
|
|
113
|
+
self.M += (value - tM) / self.k
|
|
114
|
+
self.S += (value - tM) * (value - self.M)
|
|
115
|
+
self.k += 1
|
|
116
|
+
|
|
117
|
+
def finalize(self):
|
|
118
|
+
if self.k < 3:
|
|
119
|
+
return None
|
|
120
|
+
return math.sqrt(self.S / (self.k - 2))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def synthetic_time_budget_bin(pj: dict, selected_observations: list, parameters_obs: dict):
|
|
124
|
+
"""
|
|
125
|
+
create a synthetic time budget divised in time bin
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
pj (dict): project dictionary
|
|
129
|
+
selected_observations (list): list of observations to include in time budget
|
|
130
|
+
parameters_obs (dict):
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
bool: True if everything OK
|
|
134
|
+
str: message
|
|
135
|
+
tablib.Dataset: dataset containing synthetic time budget data
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
def interval_len(interval):
|
|
139
|
+
return dec(0) if interval.empty else sum([x.upper - x.lower for x in interval])
|
|
140
|
+
|
|
141
|
+
def interval_number(interval):
|
|
142
|
+
return dec(0) if interval.empty else len(interval)
|
|
143
|
+
|
|
144
|
+
def interval_mean(interval):
|
|
145
|
+
return dec(0) if interval.empty else sum([x.upper - x.lower for x in interval]) / len(interval)
|
|
146
|
+
|
|
147
|
+
def interval_std_dev(interval) -> str:
|
|
148
|
+
if interval.empty:
|
|
149
|
+
return cfg.NA
|
|
150
|
+
else:
|
|
151
|
+
try:
|
|
152
|
+
return f"{statistics.stdev([x.upper - x.lower for x in interval]):.3f}"
|
|
153
|
+
except Exception:
|
|
154
|
+
return cfg.NA
|
|
155
|
+
|
|
156
|
+
selected_behaviors = parameters_obs[cfg.SELECTED_BEHAVIORS]
|
|
157
|
+
time_interval = parameters_obs["time"]
|
|
158
|
+
start_time = parameters_obs[cfg.START_TIME]
|
|
159
|
+
end_time = parameters_obs[cfg.END_TIME]
|
|
160
|
+
time_bin_size = dec(parameters_obs[cfg.TIME_BIN_SIZE])
|
|
161
|
+
|
|
162
|
+
parameters = [
|
|
163
|
+
["duration", "Total duration"],
|
|
164
|
+
["number", "Number of occurrences"],
|
|
165
|
+
["duration mean", "Duration mean"],
|
|
166
|
+
["duration stdev", "Duration std dev"],
|
|
167
|
+
["proportion of time", "Proportion of time"],
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
data_report = tablib.Dataset()
|
|
171
|
+
data_report.title = "Synthetic time budget with time bin"
|
|
172
|
+
|
|
173
|
+
distinct_behav_modif = []
|
|
174
|
+
for obs_id in selected_observations:
|
|
175
|
+
for event in pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]:
|
|
176
|
+
if parameters_obs[cfg.INCLUDE_MODIFIERS]:
|
|
177
|
+
if parameters_obs[cfg.EXCLUDE_NON_CODED_MODIFIERS]:
|
|
178
|
+
# get coded modifiers
|
|
179
|
+
if (
|
|
180
|
+
event[cfg.EVENT_BEHAVIOR_FIELD_IDX],
|
|
181
|
+
event[cfg.EVENT_MODIFIER_FIELD_IDX],
|
|
182
|
+
) not in distinct_behav_modif:
|
|
183
|
+
distinct_behav_modif.append((event[cfg.EVENT_BEHAVIOR_FIELD_IDX], event[cfg.EVENT_MODIFIER_FIELD_IDX]))
|
|
184
|
+
else:
|
|
185
|
+
# get all modifiers combination
|
|
186
|
+
ms: list = []
|
|
187
|
+
modifiers_list = project_functions.get_modifiers_of_behavior(pj[cfg.ETHOGRAM], event[cfg.EVENT_BEHAVIOR_FIELD_IDX])
|
|
188
|
+
if modifiers_list:
|
|
189
|
+
for modif_set in modifiers_list[0]:
|
|
190
|
+
modif_set.append("None")
|
|
191
|
+
ms.append([re.sub(r" \(.*\)", "", x) for x in modif_set])
|
|
192
|
+
distinct_modifiers = ["|".join(x) for x in itertools.product(*ms)]
|
|
193
|
+
for modifier in distinct_modifiers:
|
|
194
|
+
distinct_behav_modif.append((event[cfg.EVENT_BEHAVIOR_FIELD_IDX], modifier))
|
|
195
|
+
|
|
196
|
+
else:
|
|
197
|
+
if (event[cfg.EVENT_BEHAVIOR_FIELD_IDX], "") not in distinct_behav_modif:
|
|
198
|
+
distinct_behav_modif.append((event[cfg.EVENT_BEHAVIOR_FIELD_IDX], ""))
|
|
199
|
+
|
|
200
|
+
distinct_behav_modif.sort()
|
|
201
|
+
|
|
202
|
+
# add selected behaviors that are not observed
|
|
203
|
+
for behav in selected_behaviors:
|
|
204
|
+
if [x for x in distinct_behav_modif if x[0] == behav] == []:
|
|
205
|
+
distinct_behav_modif.append((behav, ""))
|
|
206
|
+
|
|
207
|
+
param_header = ["Observations id", "Total length (s)", "Time interval (s)"]
|
|
208
|
+
subj_header, behav_header, modif_header = (
|
|
209
|
+
[""] * len(param_header),
|
|
210
|
+
[""] * len(param_header),
|
|
211
|
+
[""] * len(param_header),
|
|
212
|
+
)
|
|
213
|
+
subj_header[1] = "Subjects:"
|
|
214
|
+
behav_header[1] = "Behaviors:"
|
|
215
|
+
modif_header[1] = "Modifiers:"
|
|
216
|
+
|
|
217
|
+
for subj in parameters_obs[cfg.SELECTED_SUBJECTS]:
|
|
218
|
+
for behavior_modifiers in distinct_behav_modif:
|
|
219
|
+
behavior, modifiers = behavior_modifiers
|
|
220
|
+
behavior_modifiers_str = "|".join(behavior_modifiers) if modifiers else behavior
|
|
221
|
+
for param in parameters:
|
|
222
|
+
subj_header.append(subj)
|
|
223
|
+
behav_header.append(behavior)
|
|
224
|
+
modif_header.append(modifiers)
|
|
225
|
+
param_header.append(param[1])
|
|
226
|
+
|
|
227
|
+
data_report.append(subj_header)
|
|
228
|
+
data_report.append(behav_header)
|
|
229
|
+
if parameters_obs[cfg.INCLUDE_MODIFIERS]:
|
|
230
|
+
data_report.append(modif_header)
|
|
231
|
+
data_report.append(param_header)
|
|
232
|
+
|
|
233
|
+
state_events_list = [
|
|
234
|
+
pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] for x in pj[cfg.ETHOGRAM] if cfg.STATE in pj[cfg.ETHOGRAM][x][cfg.TYPE].upper()
|
|
235
|
+
]
|
|
236
|
+
# select time interval
|
|
237
|
+
for obs_id in selected_observations:
|
|
238
|
+
behaviors = init_behav_modif_bin(pj[cfg.ETHOGRAM], parameters_obs[cfg.SELECTED_SUBJECTS], distinct_behav_modif, parameters)
|
|
239
|
+
|
|
240
|
+
obs_length = observation_operations.observation_total_length(pj[cfg.OBSERVATIONS][obs_id])
|
|
241
|
+
|
|
242
|
+
if obs_length == -1:
|
|
243
|
+
obs_length = 0
|
|
244
|
+
if time_interval == cfg.TIME_FULL_OBS:
|
|
245
|
+
min_time = dec(0)
|
|
246
|
+
max_time = dec(obs_length)
|
|
247
|
+
|
|
248
|
+
if time_interval == cfg.TIME_EVENTS:
|
|
249
|
+
try:
|
|
250
|
+
min_time = dec(pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS][0][0])
|
|
251
|
+
except Exception:
|
|
252
|
+
min_time = dec(0)
|
|
253
|
+
try:
|
|
254
|
+
max_time = dec(pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS][-1][0])
|
|
255
|
+
except Exception:
|
|
256
|
+
max_time = dec(obs_length)
|
|
257
|
+
|
|
258
|
+
if time_interval == cfg.TIME_ARBITRARY_INTERVAL:
|
|
259
|
+
min_time = dec(start_time)
|
|
260
|
+
max_time = dec(end_time)
|
|
261
|
+
|
|
262
|
+
if time_interval == cfg.TIME_OBS_INTERVAL:
|
|
263
|
+
obs_interval = pj[cfg.OBSERVATIONS][obs_id].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])
|
|
264
|
+
offset = pj[cfg.OBSERVATIONS][obs_id][cfg.TIME_OFFSET]
|
|
265
|
+
min_time = dec(obs_interval[0]) + offset
|
|
266
|
+
# Use max media duration for max time if no interval is defined (=0)
|
|
267
|
+
max_time = dec(obs_interval[1]) + offset if obs_interval[1] != 0 else dec(obs_length)
|
|
268
|
+
|
|
269
|
+
events_interval = {}
|
|
270
|
+
mem_events_interval = {}
|
|
271
|
+
|
|
272
|
+
for event in pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]:
|
|
273
|
+
if event[cfg.EVENT_SUBJECT_FIELD_IDX] == "":
|
|
274
|
+
current_subject = cfg.NO_FOCAL_SUBJECT
|
|
275
|
+
else:
|
|
276
|
+
current_subject = event[cfg.EVENT_SUBJECT_FIELD_IDX]
|
|
277
|
+
|
|
278
|
+
if current_subject not in parameters_obs[cfg.SELECTED_SUBJECTS]:
|
|
279
|
+
continue
|
|
280
|
+
if current_subject not in events_interval:
|
|
281
|
+
events_interval[current_subject] = {}
|
|
282
|
+
mem_events_interval[current_subject] = {}
|
|
283
|
+
|
|
284
|
+
if parameters_obs[cfg.INCLUDE_MODIFIERS]:
|
|
285
|
+
modif = event[cfg.EVENT_MODIFIER_FIELD_IDX]
|
|
286
|
+
else:
|
|
287
|
+
modif = ""
|
|
288
|
+
if (event[cfg.EVENT_BEHAVIOR_FIELD_IDX], modif) not in distinct_behav_modif:
|
|
289
|
+
continue
|
|
290
|
+
|
|
291
|
+
if (event[cfg.EVENT_BEHAVIOR_FIELD_IDX], modif) not in events_interval[current_subject]:
|
|
292
|
+
events_interval[current_subject][(event[cfg.EVENT_BEHAVIOR_FIELD_IDX], modif)] = I.empty()
|
|
293
|
+
mem_events_interval[current_subject][(event[cfg.EVENT_BEHAVIOR_FIELD_IDX], modif)] = []
|
|
294
|
+
|
|
295
|
+
if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] in state_events_list:
|
|
296
|
+
mem_events_interval[current_subject][(event[cfg.EVENT_BEHAVIOR_FIELD_IDX], modif)].append(event[cfg.EVENT_TIME_FIELD_IDX])
|
|
297
|
+
if len(mem_events_interval[current_subject][(event[cfg.EVENT_BEHAVIOR_FIELD_IDX], modif)]) == 2:
|
|
298
|
+
events_interval[current_subject][(event[cfg.EVENT_BEHAVIOR_FIELD_IDX], modif)] |= I.closedopen(
|
|
299
|
+
mem_events_interval[current_subject][(event[cfg.EVENT_BEHAVIOR_FIELD_IDX], modif)][0],
|
|
300
|
+
mem_events_interval[current_subject][(event[cfg.EVENT_BEHAVIOR_FIELD_IDX], modif)][1],
|
|
301
|
+
)
|
|
302
|
+
mem_events_interval[current_subject][(event[cfg.EVENT_BEHAVIOR_FIELD_IDX], modif)] = []
|
|
303
|
+
else:
|
|
304
|
+
events_interval[current_subject][(event[cfg.EVENT_BEHAVIOR_FIELD_IDX], modif)] |= I.singleton(
|
|
305
|
+
event[cfg.EVENT_TIME_FIELD_IDX]
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
time_bin_start = min_time
|
|
309
|
+
|
|
310
|
+
if time_bin_size:
|
|
311
|
+
time_bin_end = time_bin_start + time_bin_size
|
|
312
|
+
if time_bin_end > max_time:
|
|
313
|
+
time_bin_end = max_time
|
|
314
|
+
else:
|
|
315
|
+
time_bin_end = max_time
|
|
316
|
+
|
|
317
|
+
while True:
|
|
318
|
+
for subject in events_interval:
|
|
319
|
+
# check behavior to exclude from total time
|
|
320
|
+
time_to_subtract = 0
|
|
321
|
+
if cfg.EXCLUDED_BEHAVIORS in parameters_obs:
|
|
322
|
+
for behav in events_interval[subject]:
|
|
323
|
+
if behav[0] in parameters_obs.get(cfg.EXCLUDED_BEHAVIORS, []):
|
|
324
|
+
interval_intersec = events_interval[subject][behav] & I.closed(time_bin_start, time_bin_end)
|
|
325
|
+
time_to_subtract += interval_len(interval_intersec)
|
|
326
|
+
|
|
327
|
+
for behav in events_interval[subject]:
|
|
328
|
+
interval_intersec = events_interval[subject][behav] & I.closed(time_bin_start, time_bin_end)
|
|
329
|
+
|
|
330
|
+
nocc = interval_number(interval_intersec)
|
|
331
|
+
behaviors[subject][behav]["number"] = nocc
|
|
332
|
+
|
|
333
|
+
behav_type = project_functions.event_type(behav[0], pj[cfg.ETHOGRAM])
|
|
334
|
+
if behav_type in cfg.STATE_EVENT_TYPES:
|
|
335
|
+
dur = interval_len(interval_intersec)
|
|
336
|
+
behaviors[subject][behav]["duration"] = f"{dur:.3f}"
|
|
337
|
+
behaviors[subject][behav]["duration mean"] = f"{interval_mean(interval_intersec):.3f}"
|
|
338
|
+
behaviors[subject][behav]["duration stdev"] = interval_std_dev(interval_intersec)
|
|
339
|
+
|
|
340
|
+
if behav[0] in parameters_obs.get(cfg.EXCLUDED_BEHAVIORS, []):
|
|
341
|
+
proportion = dur / (time_bin_end - time_bin_start)
|
|
342
|
+
else:
|
|
343
|
+
proportion = dur / ((time_bin_end - time_bin_start) - time_to_subtract)
|
|
344
|
+
behaviors[subject][behav]["proportion of time"] = f"{proportion:.3f}"
|
|
345
|
+
|
|
346
|
+
if behav_type in cfg.POINT_EVENT_TYPES:
|
|
347
|
+
behaviors[subject][behav]["duration"] = cfg.NA
|
|
348
|
+
behaviors[subject][behav]["duration mean"] = cfg.NA
|
|
349
|
+
behaviors[subject][behav]["duration stdev"] = cfg.NA
|
|
350
|
+
behaviors[subject][behav]["proportion of time"] = cfg.NA
|
|
351
|
+
|
|
352
|
+
columns = [obs_id, f"{max_time - min_time:.3f}", f"{time_bin_start:.3f}-{time_bin_end:.3f}"]
|
|
353
|
+
for subject in parameters_obs[cfg.SELECTED_SUBJECTS]:
|
|
354
|
+
for behavior_modifiers in distinct_behav_modif:
|
|
355
|
+
behavior, modifiers = behavior_modifiers
|
|
356
|
+
behavior_modifiers_str = behavior_modifiers
|
|
357
|
+
|
|
358
|
+
for param in parameters:
|
|
359
|
+
columns.append(behaviors[subject][behavior_modifiers_str][param[0]])
|
|
360
|
+
|
|
361
|
+
data_report.append(columns)
|
|
362
|
+
|
|
363
|
+
time_bin_start = time_bin_end
|
|
364
|
+
time_bin_end = time_bin_start + time_bin_size
|
|
365
|
+
if time_bin_end > max_time:
|
|
366
|
+
time_bin_end = max_time
|
|
367
|
+
|
|
368
|
+
if time_bin_start == time_bin_end:
|
|
369
|
+
break
|
|
370
|
+
|
|
371
|
+
return True, data_report
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def synthetic_time_budget(pj: dict, selected_observations: list, parameters_obs: dict):
|
|
375
|
+
"""
|
|
376
|
+
create a synthetic time budget
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
pj (dict): project dictionary
|
|
380
|
+
selected_observations (list): list of observations to include in time budget
|
|
381
|
+
parameters_obs (dict):
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
bool: True if everything OK
|
|
385
|
+
str: message
|
|
386
|
+
tablib.Dataset: dataset containing synthetic time budget data
|
|
387
|
+
"""
|
|
388
|
+
|
|
389
|
+
interval = parameters_obs["time"]
|
|
390
|
+
start_time = parameters_obs[cfg.START_TIME]
|
|
391
|
+
end_time = parameters_obs[cfg.END_TIME]
|
|
392
|
+
|
|
393
|
+
parameters = [
|
|
394
|
+
["duration", "Total duration"],
|
|
395
|
+
["number", "Number of occurrences"],
|
|
396
|
+
["duration mean", "Duration mean"],
|
|
397
|
+
["duration stdev", "Duration std dev"],
|
|
398
|
+
["proportion of time", "Proportion of time"],
|
|
399
|
+
]
|
|
400
|
+
|
|
401
|
+
data_report = tablib.Dataset()
|
|
402
|
+
data_report.title = "Synthetic time budget"
|
|
403
|
+
|
|
404
|
+
ok, msg, db_connector = db_functions.load_aggregated_events_in_db(
|
|
405
|
+
pj, parameters_obs[cfg.SELECTED_SUBJECTS], selected_observations, parameters_obs[cfg.SELECTED_BEHAVIORS]
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
if not ok:
|
|
409
|
+
return False, msg, None
|
|
410
|
+
|
|
411
|
+
db_connector.create_aggregate("stdev", 1, StdevFunc)
|
|
412
|
+
cursor = db_connector.cursor()
|
|
413
|
+
|
|
414
|
+
# add selected behaviors that are not observed
|
|
415
|
+
distinct_behav_modif: list = []
|
|
416
|
+
for behavior in parameters_obs[cfg.SELECTED_BEHAVIORS]:
|
|
417
|
+
# modifiers
|
|
418
|
+
if parameters_obs[cfg.INCLUDE_MODIFIERS]:
|
|
419
|
+
if parameters_obs[cfg.EXCLUDE_NON_CODED_MODIFIERS]:
|
|
420
|
+
# get coded modifiers
|
|
421
|
+
cursor.execute("SELECT DISTINCT modifiers FROM aggregated_events WHERE behavior = ?", (behavior,))
|
|
422
|
+
for row in cursor.fetchall():
|
|
423
|
+
distinct_behav_modif.append((behavior, row["modifiers"]))
|
|
424
|
+
else:
|
|
425
|
+
# get all modifiers combination
|
|
426
|
+
ms: list = []
|
|
427
|
+
modifiers_list = project_functions.get_modifiers_of_behavior(pj[cfg.ETHOGRAM], behavior)
|
|
428
|
+
if modifiers_list:
|
|
429
|
+
for modif_set in modifiers_list[0]:
|
|
430
|
+
modif_set.append("None")
|
|
431
|
+
ms.append([re.sub(r" \(.*\)", "", x) for x in modif_set])
|
|
432
|
+
distinct_modifiers = ["|".join(x) for x in itertools.product(*ms)]
|
|
433
|
+
for modifier in distinct_modifiers:
|
|
434
|
+
distinct_behav_modif.append((behavior, modifier))
|
|
435
|
+
|
|
436
|
+
else:
|
|
437
|
+
distinct_behav_modif.append((behavior, ""))
|
|
438
|
+
|
|
439
|
+
# print(f"{distinct_behav_modif=}")
|
|
440
|
+
|
|
441
|
+
param_header = ["Observations id", "Total length (s)"]
|
|
442
|
+
subj_header, behav_header, modif_header = (
|
|
443
|
+
[""] * len(param_header),
|
|
444
|
+
[""] * len(param_header),
|
|
445
|
+
[""] * len(param_header),
|
|
446
|
+
)
|
|
447
|
+
subj_header[1] = "Subjects:"
|
|
448
|
+
behav_header[1] = "Behaviors:"
|
|
449
|
+
modif_header[1] = "Modifiers:"
|
|
450
|
+
|
|
451
|
+
for subj in parameters_obs[cfg.SELECTED_SUBJECTS]:
|
|
452
|
+
for behavior, modifiers in distinct_behav_modif:
|
|
453
|
+
"""behavior, modifiers = behavior_modifiers"""
|
|
454
|
+
"""behavior_modifiers_str = "|".join(behavior_modifiers) if modifiers else behavior"""
|
|
455
|
+
for param in parameters:
|
|
456
|
+
subj_header.append(subj)
|
|
457
|
+
behav_header.append(behavior)
|
|
458
|
+
modif_header.append(modifiers)
|
|
459
|
+
param_header.append(param[1])
|
|
460
|
+
|
|
461
|
+
data_report.append(subj_header)
|
|
462
|
+
data_report.append(behav_header)
|
|
463
|
+
if parameters_obs[cfg.INCLUDE_MODIFIERS]:
|
|
464
|
+
data_report.append(modif_header)
|
|
465
|
+
data_report.append(param_header)
|
|
466
|
+
|
|
467
|
+
# select time interval
|
|
468
|
+
for obs_id in selected_observations:
|
|
469
|
+
behaviors = init_behav_modif(pj[cfg.ETHOGRAM], parameters_obs[cfg.SELECTED_SUBJECTS], distinct_behav_modif, parameters)
|
|
470
|
+
|
|
471
|
+
ok, msg, db_connector = db_functions.load_aggregated_events_in_db(
|
|
472
|
+
pj, parameters_obs[cfg.SELECTED_SUBJECTS], [obs_id], parameters_obs[cfg.SELECTED_BEHAVIORS]
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
if not ok:
|
|
476
|
+
return False, msg, None
|
|
477
|
+
|
|
478
|
+
db_connector.create_aggregate("stdev", 1, StdevFunc)
|
|
479
|
+
cursor = db_connector.cursor()
|
|
480
|
+
# if modifiers not to be included set modifiers to ""
|
|
481
|
+
if not parameters_obs[cfg.INCLUDE_MODIFIERS]:
|
|
482
|
+
cursor.execute("UPDATE aggregated_events SET modifiers = ''")
|
|
483
|
+
|
|
484
|
+
# time
|
|
485
|
+
obs_length = observation_operations.observation_total_length(pj[cfg.OBSERVATIONS][obs_id])
|
|
486
|
+
|
|
487
|
+
if obs_length == dec(-1): # media length not available
|
|
488
|
+
interval = cfg.TIME_EVENTS
|
|
489
|
+
|
|
490
|
+
if obs_length == dec(-2): # images obs without time
|
|
491
|
+
interval = cfg.TIME_EVENTS
|
|
492
|
+
|
|
493
|
+
if interval == cfg.TIME_FULL_OBS:
|
|
494
|
+
min_time = float(0)
|
|
495
|
+
max_time = float(obs_length)
|
|
496
|
+
|
|
497
|
+
if interval == cfg.TIME_EVENTS:
|
|
498
|
+
try:
|
|
499
|
+
min_time = float(pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS][0][0])
|
|
500
|
+
except Exception:
|
|
501
|
+
min_time = float(0)
|
|
502
|
+
try:
|
|
503
|
+
max_time = float(pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS][-1][0])
|
|
504
|
+
except Exception:
|
|
505
|
+
max_time = float(obs_length)
|
|
506
|
+
|
|
507
|
+
if interval == cfg.TIME_ARBITRARY_INTERVAL:
|
|
508
|
+
min_time = float(start_time)
|
|
509
|
+
max_time = float(end_time)
|
|
510
|
+
|
|
511
|
+
if obs_length != dec(-2): # # obs not an images obs without time
|
|
512
|
+
# adapt start and stop to the selected time interval
|
|
513
|
+
cursor.execute(
|
|
514
|
+
"UPDATE aggregated_events SET start = ? WHERE observation = ? AND start < ? AND stop BETWEEN ? AND ?",
|
|
515
|
+
(
|
|
516
|
+
min_time,
|
|
517
|
+
obs_id,
|
|
518
|
+
min_time,
|
|
519
|
+
min_time,
|
|
520
|
+
max_time,
|
|
521
|
+
),
|
|
522
|
+
)
|
|
523
|
+
cursor.execute(
|
|
524
|
+
"UPDATE aggregated_events SET stop = ? WHERE observation = ? AND stop > ? AND start BETWEEN ? AND ?",
|
|
525
|
+
(
|
|
526
|
+
max_time,
|
|
527
|
+
obs_id,
|
|
528
|
+
max_time,
|
|
529
|
+
min_time,
|
|
530
|
+
max_time,
|
|
531
|
+
),
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
cursor.execute(
|
|
535
|
+
"UPDATE aggregated_events SET start = ?, stop = ? WHERE observation = ? AND start < ? AND stop > ?",
|
|
536
|
+
(
|
|
537
|
+
min_time,
|
|
538
|
+
max_time,
|
|
539
|
+
obs_id,
|
|
540
|
+
min_time,
|
|
541
|
+
max_time,
|
|
542
|
+
),
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
cursor.execute(
|
|
546
|
+
"DELETE FROM aggregated_events WHERE observation = ? AND (start < ? AND stop < ?) OR (start > ? AND stop > ?)",
|
|
547
|
+
(
|
|
548
|
+
obs_id,
|
|
549
|
+
min_time,
|
|
550
|
+
min_time,
|
|
551
|
+
max_time,
|
|
552
|
+
max_time,
|
|
553
|
+
),
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
for subject in parameters_obs[cfg.SELECTED_SUBJECTS]:
|
|
557
|
+
# check if behaviors are to exclude from total time
|
|
558
|
+
time_to_subtract = 0
|
|
559
|
+
if obs_length != dec(-2): # obs not an images obs without time
|
|
560
|
+
if cfg.EXCLUDED_BEHAVIORS in parameters_obs:
|
|
561
|
+
for excluded_behav in parameters_obs[cfg.EXCLUDED_BEHAVIORS]:
|
|
562
|
+
cursor.execute(
|
|
563
|
+
("SELECT SUM(stop-start) FROM aggregated_events WHERE observation = ? AND subject = ? AND behavior = ? "),
|
|
564
|
+
(
|
|
565
|
+
obs_id,
|
|
566
|
+
subject,
|
|
567
|
+
excluded_behav,
|
|
568
|
+
),
|
|
569
|
+
)
|
|
570
|
+
for row in cursor.fetchall():
|
|
571
|
+
if row[0] is not None:
|
|
572
|
+
time_to_subtract += row[0]
|
|
573
|
+
|
|
574
|
+
for behavior_modifiers in distinct_behav_modif:
|
|
575
|
+
behavior, modifiers = behavior_modifiers
|
|
576
|
+
behavior_modifiers_str = "|".join(behavior_modifiers) if modifiers else behavior
|
|
577
|
+
|
|
578
|
+
cursor.execute(
|
|
579
|
+
(
|
|
580
|
+
"SELECT SUM(stop - start) AS duration, "
|
|
581
|
+
"COUNT(*) AS n_occurences, "
|
|
582
|
+
"AVG(stop - start) AS mean, "
|
|
583
|
+
"stdev(stop - start) AS ST_DEV, type "
|
|
584
|
+
"FROM aggregated_events "
|
|
585
|
+
"WHERE observation = ? AND subject = ? AND behavior = ? AND modifiers = ? "
|
|
586
|
+
),
|
|
587
|
+
(
|
|
588
|
+
obs_id,
|
|
589
|
+
subject,
|
|
590
|
+
behavior,
|
|
591
|
+
modifiers,
|
|
592
|
+
),
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
for row in cursor.fetchall():
|
|
596
|
+
behaviors[subject][behavior_modifiers_str]["number"] = 0 if row["n_occurences"] is None else row["n_occurences"]
|
|
597
|
+
|
|
598
|
+
if obs_length == dec(-2): # images obs without time
|
|
599
|
+
behaviors[subject][behavior_modifiers_str]["duration"] = cfg.NA
|
|
600
|
+
behaviors[subject][behavior_modifiers_str]["duration mean"] = cfg.NA
|
|
601
|
+
behaviors[subject][behavior_modifiers_str]["duration stdev"] = cfg.NA
|
|
602
|
+
behaviors[subject][behavior_modifiers_str]["proportion of time"] = cfg.NA
|
|
603
|
+
|
|
604
|
+
else:
|
|
605
|
+
if row["type"] == cfg.POINT:
|
|
606
|
+
behaviors[subject][behavior_modifiers_str]["duration"] = cfg.NA
|
|
607
|
+
behaviors[subject][behavior_modifiers_str]["duration mean"] = cfg.NA
|
|
608
|
+
behaviors[subject][behavior_modifiers_str]["duration stdev"] = cfg.NA
|
|
609
|
+
behaviors[subject][behavior_modifiers_str]["proportion of time"] = cfg.NA
|
|
610
|
+
|
|
611
|
+
if row["type"] == cfg.STATE:
|
|
612
|
+
behaviors[subject][behavior_modifiers_str]["duration"] = (
|
|
613
|
+
cfg.NA if row["duration"] is None else f"{row['duration']:.3f}"
|
|
614
|
+
)
|
|
615
|
+
behaviors[subject][behavior_modifiers_str]["duration mean"] = (
|
|
616
|
+
cfg.NA if row["mean"] is None else f"{row['mean']:.3f}"
|
|
617
|
+
)
|
|
618
|
+
behaviors[subject][behavior_modifiers_str]["duration stdev"] = (
|
|
619
|
+
cfg.NA if row["ST_DEV"] is None else f"{row['ST_DEV']:.3f}"
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
if behavior not in parameters_obs[cfg.EXCLUDED_BEHAVIORS]:
|
|
623
|
+
try:
|
|
624
|
+
behaviors[subject][behavior_modifiers_str]["proportion of time"] = (
|
|
625
|
+
cfg.NA
|
|
626
|
+
if row["duration"] is None
|
|
627
|
+
else f"{row['duration'] / ((max_time - min_time) - time_to_subtract):.3f}"
|
|
628
|
+
)
|
|
629
|
+
except ZeroDivisionError:
|
|
630
|
+
behaviors[subject][behavior_modifiers_str]["proportion of time"] = cfg.NA
|
|
631
|
+
else:
|
|
632
|
+
# behavior subtracted
|
|
633
|
+
behaviors[subject][behavior_modifiers_str]["proportion of time"] = (
|
|
634
|
+
cfg.NA if row["duration"] is None else f"{row['duration'] / (max_time - min_time):.3f}"
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
if obs_length == dec(-2):
|
|
638
|
+
columns = [obs_id, cfg.NA]
|
|
639
|
+
else:
|
|
640
|
+
columns = [obs_id, f"{max_time - min_time:0.3f}"]
|
|
641
|
+
for subj in parameters_obs[cfg.SELECTED_SUBJECTS]:
|
|
642
|
+
for behavior_modifiers in distinct_behav_modif:
|
|
643
|
+
behavior, modifiers = behavior_modifiers
|
|
644
|
+
behavior_modifiers_str = "|".join(behavior_modifiers) if modifiers else behavior
|
|
645
|
+
|
|
646
|
+
for param in parameters:
|
|
647
|
+
columns.append(behaviors[subj][behavior_modifiers_str][param[0]])
|
|
648
|
+
|
|
649
|
+
data_report.append(columns)
|
|
650
|
+
|
|
651
|
+
return True, msg, data_report
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def time_budget_analysis(
|
|
655
|
+
ethogram: dict, cursor, selected_observations: list, parameters: dict, by_category: bool = False
|
|
656
|
+
) -> Tuple[list, dict]:
|
|
657
|
+
"""
|
|
658
|
+
extract number of occurrences, total duration, mean ...
|
|
659
|
+
if start_time = 0 and end_time = 0 all events are extracted
|
|
660
|
+
|
|
661
|
+
Args:
|
|
662
|
+
ethogram (dict): project ethogram
|
|
663
|
+
cursor: cursor on temporary database
|
|
664
|
+
selected_observations (list): selected observations
|
|
665
|
+
parameters (dict): parameters for analysis
|
|
666
|
+
by_category (bool): True for grouping in behavioral category else False
|
|
667
|
+
|
|
668
|
+
Returns:
|
|
669
|
+
list: results
|
|
670
|
+
dict:
|
|
671
|
+
"""
|
|
672
|
+
|
|
673
|
+
logging.debug("time_budget_analysis function")
|
|
674
|
+
|
|
675
|
+
logging.debug(f"{selected_observations=}")
|
|
676
|
+
logging.debug(f"{parameters=}")
|
|
677
|
+
|
|
678
|
+
categories: dict = {}
|
|
679
|
+
out: list = []
|
|
680
|
+
for subject in parameters[cfg.SELECTED_SUBJECTS]:
|
|
681
|
+
logging.debug(f"{subject=}")
|
|
682
|
+
|
|
683
|
+
out_cat: list = []
|
|
684
|
+
categories[subject] = {}
|
|
685
|
+
|
|
686
|
+
for behavior in parameters[cfg.SELECTED_BEHAVIORS]:
|
|
687
|
+
logging.debug(f"{behavior=}")
|
|
688
|
+
|
|
689
|
+
if parameters[cfg.INCLUDE_MODIFIERS]: # with modifiers
|
|
690
|
+
if parameters[cfg.EXCLUDE_NON_CODED_MODIFIERS]:
|
|
691
|
+
# get coded modifiers
|
|
692
|
+
cursor.execute("SELECT DISTINCT modifiers FROM events WHERE subject = ? AND code = ?", (subject, behavior))
|
|
693
|
+
distinct_modifiers = [x[0] for x in cursor.fetchall()]
|
|
694
|
+
else:
|
|
695
|
+
# get all modifiers combinations
|
|
696
|
+
ms: list = []
|
|
697
|
+
modifiers_list = project_functions.get_modifiers_of_behavior(ethogram, behavior)
|
|
698
|
+
if modifiers_list:
|
|
699
|
+
for modif_set in modifiers_list[0]:
|
|
700
|
+
modif_set.append("None")
|
|
701
|
+
ms.append([re.sub(r" \(.*\)", "", x) for x in modif_set])
|
|
702
|
+
distinct_modifiers = ["|".join(x) for x in itertools.product(*ms)]
|
|
703
|
+
|
|
704
|
+
if not distinct_modifiers:
|
|
705
|
+
if not parameters[cfg.EXCLUDE_BEHAVIORS]:
|
|
706
|
+
if project_functions.event_type(behavior, ethogram) in cfg.STATE_EVENT_TYPES:
|
|
707
|
+
# check if observation from pictures
|
|
708
|
+
if parameters["start time"] == dec("0.000") and parameters["end time"] == dec("0.000"):
|
|
709
|
+
duration = cfg.NA
|
|
710
|
+
else:
|
|
711
|
+
duration = 0.000
|
|
712
|
+
|
|
713
|
+
out_cat.append(
|
|
714
|
+
{
|
|
715
|
+
"subject": subject,
|
|
716
|
+
"behavior": behavior,
|
|
717
|
+
"modifiers": "",
|
|
718
|
+
"duration": duration,
|
|
719
|
+
"duration_mean": cfg.NA,
|
|
720
|
+
"duration_stdev": cfg.NA,
|
|
721
|
+
"number": "0",
|
|
722
|
+
"inter_duration_mean": cfg.NA,
|
|
723
|
+
"inter_duration_stdev": cfg.NA,
|
|
724
|
+
}
|
|
725
|
+
)
|
|
726
|
+
else: # point
|
|
727
|
+
out_cat.append(
|
|
728
|
+
{
|
|
729
|
+
"subject": subject,
|
|
730
|
+
"behavior": behavior,
|
|
731
|
+
"modifiers": "",
|
|
732
|
+
"duration": cfg.NA,
|
|
733
|
+
"duration_mean": cfg.NA,
|
|
734
|
+
"duration_stdev": cfg.NA,
|
|
735
|
+
"number": "0",
|
|
736
|
+
"inter_duration_mean": cfg.NA,
|
|
737
|
+
"inter_duration_stdev": cfg.NA,
|
|
738
|
+
}
|
|
739
|
+
)
|
|
740
|
+
continue
|
|
741
|
+
|
|
742
|
+
if project_functions.event_type(behavior, ethogram) in cfg.POINT_EVENT_TYPES:
|
|
743
|
+
for modifier in distinct_modifiers:
|
|
744
|
+
cursor.execute(
|
|
745
|
+
(
|
|
746
|
+
"SELECT occurence, observation FROM events "
|
|
747
|
+
"WHERE subject = ? "
|
|
748
|
+
"AND code = ? "
|
|
749
|
+
"AND modifiers = ? "
|
|
750
|
+
"ORDER BY observation, occurence"
|
|
751
|
+
),
|
|
752
|
+
(subject, behavior, modifier),
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
rows = cursor.fetchall()
|
|
756
|
+
|
|
757
|
+
if len(selected_observations) == 1:
|
|
758
|
+
new_rows: list = []
|
|
759
|
+
for occurence, observation in rows:
|
|
760
|
+
if occurence is None:
|
|
761
|
+
new_rows.append([float("NaN"), observation])
|
|
762
|
+
else:
|
|
763
|
+
new_rows.append([occurence, observation])
|
|
764
|
+
rows = list(new_rows)
|
|
765
|
+
|
|
766
|
+
# include behaviors without events
|
|
767
|
+
if len(rows) == 0:
|
|
768
|
+
if not parameters[cfg.EXCLUDE_BEHAVIORS]:
|
|
769
|
+
out_cat.append(
|
|
770
|
+
{
|
|
771
|
+
"subject": subject,
|
|
772
|
+
"behavior": behavior,
|
|
773
|
+
"modifiers": "",
|
|
774
|
+
"duration": cfg.NA,
|
|
775
|
+
"duration_mean": cfg.NA,
|
|
776
|
+
"duration_stdev": cfg.NA,
|
|
777
|
+
"number": 0,
|
|
778
|
+
"inter_duration_mean": cfg.NA,
|
|
779
|
+
"inter_duration_stdev": cfg.NA,
|
|
780
|
+
}
|
|
781
|
+
)
|
|
782
|
+
continue
|
|
783
|
+
|
|
784
|
+
# inter events duration
|
|
785
|
+
all_event_interdurations = []
|
|
786
|
+
for idx, row in enumerate(rows):
|
|
787
|
+
if idx and row[1] == rows[idx - 1][1]:
|
|
788
|
+
all_event_interdurations.append(float(row[0]) - float(rows[idx - 1][0]))
|
|
789
|
+
|
|
790
|
+
if [x for x in all_event_interdurations if math.isnan(x)] or len(all_event_interdurations) == 0:
|
|
791
|
+
inter_duration_mean = cfg.NA
|
|
792
|
+
inter_duration_stdev = cfg.NA
|
|
793
|
+
else:
|
|
794
|
+
inter_duration_mean = round(statistics.mean(all_event_interdurations), 3)
|
|
795
|
+
if len(all_event_interdurations) > 1:
|
|
796
|
+
inter_duration_stdev = round(statistics.stdev(all_event_interdurations), 3)
|
|
797
|
+
else:
|
|
798
|
+
inter_duration_stdev = cfg.NA
|
|
799
|
+
|
|
800
|
+
out_cat.append(
|
|
801
|
+
{
|
|
802
|
+
"subject": subject,
|
|
803
|
+
"behavior": behavior,
|
|
804
|
+
"modifiers": modifier,
|
|
805
|
+
"duration": cfg.NA,
|
|
806
|
+
"duration_mean": cfg.NA,
|
|
807
|
+
"duration_stdev": cfg.NA,
|
|
808
|
+
"number": len(rows),
|
|
809
|
+
"inter_duration_mean": inter_duration_mean,
|
|
810
|
+
"inter_duration_stdev": inter_duration_stdev,
|
|
811
|
+
}
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
if project_functions.event_type(behavior, ethogram) in cfg.STATE_EVENT_TYPES:
|
|
815
|
+
for modifier in distinct_modifiers:
|
|
816
|
+
cursor.execute(
|
|
817
|
+
(
|
|
818
|
+
"SELECT occurence, observation FROM events "
|
|
819
|
+
"WHERE subject = ? "
|
|
820
|
+
"AND code = ? "
|
|
821
|
+
"AND modifiers = ? "
|
|
822
|
+
"ORDER BY observation, occurence"
|
|
823
|
+
),
|
|
824
|
+
(subject, behavior, modifier),
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
rows = list(cursor.fetchall())
|
|
828
|
+
|
|
829
|
+
if len(rows) == 0:
|
|
830
|
+
if not parameters[cfg.EXCLUDE_BEHAVIORS]: # include behaviors without events
|
|
831
|
+
# check if observation from pictures
|
|
832
|
+
if parameters["start time"] == dec("0.000") and parameters["end time"] == dec("0.000"):
|
|
833
|
+
duration = cfg.NA
|
|
834
|
+
else:
|
|
835
|
+
duration: float = 0.000
|
|
836
|
+
out_cat.append(
|
|
837
|
+
{
|
|
838
|
+
"subject": subject,
|
|
839
|
+
"behavior": behavior,
|
|
840
|
+
"modifiers": modifier,
|
|
841
|
+
"duration": duration,
|
|
842
|
+
"duration_mean": cfg.NA,
|
|
843
|
+
"duration_stdev": cfg.NA,
|
|
844
|
+
"number": 0,
|
|
845
|
+
"inter_duration_mean": cfg.NA,
|
|
846
|
+
"inter_duration_stdev": cfg.NA,
|
|
847
|
+
}
|
|
848
|
+
)
|
|
849
|
+
continue
|
|
850
|
+
|
|
851
|
+
if len(rows) % 2:
|
|
852
|
+
out_cat.append(
|
|
853
|
+
{
|
|
854
|
+
"subject": subject,
|
|
855
|
+
"behavior": behavior,
|
|
856
|
+
"modifiers": modifier,
|
|
857
|
+
"duration": cfg.UNPAIRED,
|
|
858
|
+
"duration_mean": cfg.UNPAIRED,
|
|
859
|
+
"duration_stdev": cfg.UNPAIRED,
|
|
860
|
+
"number": cfg.UNPAIRED,
|
|
861
|
+
"inter_duration_mean": cfg.UNPAIRED,
|
|
862
|
+
"inter_duration_stdev": cfg.UNPAIRED,
|
|
863
|
+
}
|
|
864
|
+
)
|
|
865
|
+
else:
|
|
866
|
+
all_event_durations: list = []
|
|
867
|
+
all_event_interdurations: list = []
|
|
868
|
+
for idx, row in enumerate(rows):
|
|
869
|
+
# event
|
|
870
|
+
if idx % 2 == 0:
|
|
871
|
+
if row[0] is not None and rows[idx + 1][0] is not None:
|
|
872
|
+
new_init, new_end = float(row[0]), float(rows[idx + 1][0])
|
|
873
|
+
all_event_durations.append(new_end - new_init)
|
|
874
|
+
else:
|
|
875
|
+
all_event_durations.append(float("NaN"))
|
|
876
|
+
|
|
877
|
+
# inter event if same observation
|
|
878
|
+
if idx % 2 and idx != len(rows) - 1 and row[1] == rows[idx + 1][1]:
|
|
879
|
+
if row[0] is not None and rows[idx + 1][0] is not None:
|
|
880
|
+
# and (
|
|
881
|
+
# parameters["start time"] <= row[0] <= parameters["end time"]
|
|
882
|
+
# and parameters["start time"] <= rows[idx + 1][0] <= parameters["end time"]
|
|
883
|
+
# ):
|
|
884
|
+
all_event_interdurations.append(float(rows[idx + 1][0]) - float(row[0]))
|
|
885
|
+
else:
|
|
886
|
+
all_event_interdurations.append(float("NaN"))
|
|
887
|
+
|
|
888
|
+
# events
|
|
889
|
+
if [x for x in all_event_durations if math.isnan(x)]:
|
|
890
|
+
total_duration = cfg.NA
|
|
891
|
+
else:
|
|
892
|
+
total_duration = round(sum(all_event_durations), 3)
|
|
893
|
+
if [x for x in all_event_durations if math.isnan(x)] or len(all_event_durations) == 0:
|
|
894
|
+
duration_mean = cfg.NA
|
|
895
|
+
duration_stdev = cfg.NA
|
|
896
|
+
else:
|
|
897
|
+
duration_mean = round(statistics.mean(all_event_durations), 3)
|
|
898
|
+
if len(all_event_durations) > 1:
|
|
899
|
+
duration_stdev = round(statistics.stdev(all_event_durations), 3)
|
|
900
|
+
else:
|
|
901
|
+
duration_stdev = cfg.NA
|
|
902
|
+
# interduration
|
|
903
|
+
if [x for x in all_event_interdurations if math.isnan(x)] or len(all_event_interdurations) == 0:
|
|
904
|
+
inter_duration_mean = cfg.NA
|
|
905
|
+
inter_duration_stdev = cfg.NA
|
|
906
|
+
else:
|
|
907
|
+
inter_duration_mean = round(statistics.mean(all_event_interdurations), 3)
|
|
908
|
+
if len(all_event_interdurations) > 1:
|
|
909
|
+
inter_duration_stdev = round(statistics.stdev(all_event_interdurations), 3)
|
|
910
|
+
else:
|
|
911
|
+
inter_duration_stdev = cfg.NA
|
|
912
|
+
|
|
913
|
+
out_cat.append(
|
|
914
|
+
{
|
|
915
|
+
"subject": subject,
|
|
916
|
+
"behavior": behavior,
|
|
917
|
+
"modifiers": modifier,
|
|
918
|
+
"duration": total_duration,
|
|
919
|
+
"duration_mean": duration_mean,
|
|
920
|
+
"duration_stdev": duration_stdev,
|
|
921
|
+
"number": len(all_event_durations),
|
|
922
|
+
"inter_duration_mean": inter_duration_mean,
|
|
923
|
+
"inter_duration_stdev": inter_duration_stdev,
|
|
924
|
+
}
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
else: # no modifiers
|
|
928
|
+
if project_functions.event_type(behavior, ethogram) in cfg.POINT_EVENT_TYPES:
|
|
929
|
+
cursor.execute(
|
|
930
|
+
("SELECT occurence, observation FROM events WHERE subject = ? AND code = ? ORDER BY observation, occurence"),
|
|
931
|
+
(subject, behavior),
|
|
932
|
+
)
|
|
933
|
+
|
|
934
|
+
rows = list(cursor.fetchall())
|
|
935
|
+
|
|
936
|
+
if len(selected_observations) == 1:
|
|
937
|
+
new_rows: list = []
|
|
938
|
+
for occurence, observation in rows:
|
|
939
|
+
if occurence is None:
|
|
940
|
+
new_rows.append([float("NaN"), observation])
|
|
941
|
+
else:
|
|
942
|
+
new_rows.append([occurence, observation])
|
|
943
|
+
rows = list(new_rows)
|
|
944
|
+
|
|
945
|
+
# include behaviors without events
|
|
946
|
+
if len(rows) == 0:
|
|
947
|
+
if not parameters[cfg.EXCLUDE_BEHAVIORS]:
|
|
948
|
+
out_cat.append(
|
|
949
|
+
{
|
|
950
|
+
"subject": subject,
|
|
951
|
+
"behavior": behavior,
|
|
952
|
+
"modifiers": "",
|
|
953
|
+
"duration": cfg.NA,
|
|
954
|
+
"duration_mean": cfg.NA,
|
|
955
|
+
"duration_stdev": cfg.NA,
|
|
956
|
+
"number": 0,
|
|
957
|
+
"inter_duration_mean": cfg.NA,
|
|
958
|
+
"inter_duration_stdev": cfg.NA,
|
|
959
|
+
}
|
|
960
|
+
)
|
|
961
|
+
continue
|
|
962
|
+
|
|
963
|
+
# inter events duration
|
|
964
|
+
all_event_interdurations = []
|
|
965
|
+
for idx, row in enumerate(rows):
|
|
966
|
+
if idx and row[1] == rows[idx - 1][1]:
|
|
967
|
+
all_event_interdurations.append(float(row[0]) - float(rows[idx - 1][0]))
|
|
968
|
+
|
|
969
|
+
if [x for x in all_event_interdurations if math.isnan(x)] or len(all_event_interdurations) == 0:
|
|
970
|
+
inter_duration_mean = cfg.NA
|
|
971
|
+
inter_duration_stdev = cfg.NA
|
|
972
|
+
else:
|
|
973
|
+
inter_duration_mean = round(statistics.mean(all_event_interdurations), 3)
|
|
974
|
+
if len(all_event_interdurations) > 1:
|
|
975
|
+
inter_duration_stdev = round(statistics.stdev(all_event_interdurations), 3)
|
|
976
|
+
else:
|
|
977
|
+
inter_duration_stdev = cfg.NA
|
|
978
|
+
|
|
979
|
+
out_cat.append(
|
|
980
|
+
{
|
|
981
|
+
"subject": subject,
|
|
982
|
+
"behavior": behavior,
|
|
983
|
+
"modifiers": "",
|
|
984
|
+
"duration": cfg.NA,
|
|
985
|
+
"duration_mean": cfg.NA,
|
|
986
|
+
"duration_stdev": cfg.NA,
|
|
987
|
+
"number": len(rows),
|
|
988
|
+
"inter_duration_mean": inter_duration_mean,
|
|
989
|
+
"inter_duration_stdev": inter_duration_stdev,
|
|
990
|
+
}
|
|
991
|
+
)
|
|
992
|
+
|
|
993
|
+
if project_functions.event_type(behavior, ethogram) in cfg.STATE_EVENT_TYPES:
|
|
994
|
+
cursor.execute(
|
|
995
|
+
("SELECT occurence, observation FROM events WHERE subject = ? AND code = ? ORDER BY observation, occurence"),
|
|
996
|
+
(subject, behavior),
|
|
997
|
+
)
|
|
998
|
+
|
|
999
|
+
rows = list(cursor.fetchall())
|
|
1000
|
+
|
|
1001
|
+
if len(rows) == 0:
|
|
1002
|
+
if not parameters[cfg.EXCLUDE_BEHAVIORS]: # include behaviors without events
|
|
1003
|
+
# check if observation from pictures
|
|
1004
|
+
if parameters["start time"] == dec("0.000") and parameters["end time"] == dec("0.000"):
|
|
1005
|
+
duration = cfg.NA
|
|
1006
|
+
else:
|
|
1007
|
+
duration = 0.000
|
|
1008
|
+
out_cat.append(
|
|
1009
|
+
{
|
|
1010
|
+
"subject": subject,
|
|
1011
|
+
"behavior": behavior,
|
|
1012
|
+
"modifiers": "",
|
|
1013
|
+
"duration": duration,
|
|
1014
|
+
"duration_mean": cfg.NA,
|
|
1015
|
+
"duration_stdev": cfg.NA,
|
|
1016
|
+
"number": 0,
|
|
1017
|
+
"inter_duration_mean": cfg.NA,
|
|
1018
|
+
"inter_duration_stdev": cfg.NA,
|
|
1019
|
+
}
|
|
1020
|
+
)
|
|
1021
|
+
continue
|
|
1022
|
+
|
|
1023
|
+
if len(rows) % 2: # unpaired events
|
|
1024
|
+
out_cat.append(
|
|
1025
|
+
{
|
|
1026
|
+
"subject": subject,
|
|
1027
|
+
"behavior": behavior,
|
|
1028
|
+
"modifiers": "",
|
|
1029
|
+
"duration": cfg.UNPAIRED,
|
|
1030
|
+
"duration_mean": cfg.UNPAIRED,
|
|
1031
|
+
"duration_stdev": cfg.UNPAIRED,
|
|
1032
|
+
"number": cfg.UNPAIRED,
|
|
1033
|
+
"inter_duration_mean": cfg.UNPAIRED,
|
|
1034
|
+
"inter_duration_stdev": cfg.UNPAIRED,
|
|
1035
|
+
}
|
|
1036
|
+
)
|
|
1037
|
+
else:
|
|
1038
|
+
all_event_durations: list = []
|
|
1039
|
+
all_event_interdurations: list = []
|
|
1040
|
+
for idx, row in enumerate(rows):
|
|
1041
|
+
# event
|
|
1042
|
+
if idx % 2 == 0:
|
|
1043
|
+
if row[0] is not None and rows[idx + 1][0] is not None:
|
|
1044
|
+
new_init, new_end = float(row[0]), float(rows[idx + 1][0])
|
|
1045
|
+
all_event_durations.append(new_end - new_init)
|
|
1046
|
+
else:
|
|
1047
|
+
all_event_durations.append(float("NaN"))
|
|
1048
|
+
|
|
1049
|
+
# inter event if same observation
|
|
1050
|
+
if idx % 2 and idx != len(rows) - 1 and row[1] == rows[idx + 1][1]:
|
|
1051
|
+
if row[0] is not None and rows[idx + 1][0] is not None:
|
|
1052
|
+
# and (
|
|
1053
|
+
# parameters["start time"] <= row[0] <= parameters["end time"]
|
|
1054
|
+
# and parameters["start time"] <= rows[idx + 1][0] <= parameters["end time"]
|
|
1055
|
+
# ):
|
|
1056
|
+
all_event_interdurations.append(float(rows[idx + 1][0]) - float(row[0]))
|
|
1057
|
+
else:
|
|
1058
|
+
all_event_interdurations.append(float("NaN"))
|
|
1059
|
+
|
|
1060
|
+
# events
|
|
1061
|
+
if [x for x in all_event_durations if math.isnan(x)]:
|
|
1062
|
+
total_duration = cfg.NA
|
|
1063
|
+
else:
|
|
1064
|
+
total_duration = round(sum(all_event_durations), 3)
|
|
1065
|
+
if [x for x in all_event_durations if math.isnan(x)] or len(all_event_durations) == 0:
|
|
1066
|
+
duration_mean = cfg.NA
|
|
1067
|
+
duration_stdev = cfg.NA
|
|
1068
|
+
else:
|
|
1069
|
+
duration_mean = round(statistics.mean(all_event_durations), 3)
|
|
1070
|
+
if len(all_event_durations) > 1:
|
|
1071
|
+
duration_stdev = round(statistics.stdev(all_event_durations), 3)
|
|
1072
|
+
else:
|
|
1073
|
+
duration_stdev = cfg.NA
|
|
1074
|
+
# interduration
|
|
1075
|
+
if [x for x in all_event_interdurations if math.isnan(x)] or len(all_event_interdurations) == 0:
|
|
1076
|
+
inter_duration_mean = cfg.NA
|
|
1077
|
+
inter_duration_stdev = cfg.NA
|
|
1078
|
+
else:
|
|
1079
|
+
inter_duration_mean = round(statistics.mean(all_event_interdurations), 3)
|
|
1080
|
+
if len(all_event_interdurations) > 1:
|
|
1081
|
+
inter_duration_stdev = round(statistics.stdev(all_event_interdurations), 3)
|
|
1082
|
+
else:
|
|
1083
|
+
inter_duration_stdev = cfg.NA
|
|
1084
|
+
|
|
1085
|
+
out_cat.append(
|
|
1086
|
+
{
|
|
1087
|
+
"subject": subject,
|
|
1088
|
+
"behavior": behavior,
|
|
1089
|
+
"modifiers": "",
|
|
1090
|
+
"duration": total_duration,
|
|
1091
|
+
"duration_mean": duration_mean,
|
|
1092
|
+
"duration_stdev": duration_stdev,
|
|
1093
|
+
"number": len(all_event_durations),
|
|
1094
|
+
"inter_duration_mean": inter_duration_mean,
|
|
1095
|
+
"inter_duration_stdev": inter_duration_stdev,
|
|
1096
|
+
}
|
|
1097
|
+
)
|
|
1098
|
+
|
|
1099
|
+
out += out_cat
|
|
1100
|
+
|
|
1101
|
+
if by_category: # and flagCategories:
|
|
1102
|
+
for behav in out_cat:
|
|
1103
|
+
try:
|
|
1104
|
+
category = [
|
|
1105
|
+
ethogram[x][cfg.BEHAVIOR_CATEGORY]
|
|
1106
|
+
for x in ethogram
|
|
1107
|
+
if cfg.BEHAVIOR_CATEGORY in ethogram[x] and ethogram[x][cfg.BEHAVIOR_CODE] == behav["behavior"]
|
|
1108
|
+
][0]
|
|
1109
|
+
except Exception:
|
|
1110
|
+
category = ""
|
|
1111
|
+
|
|
1112
|
+
if category not in categories[subject]:
|
|
1113
|
+
categories[subject][category] = {"duration": 0, "number": 0}
|
|
1114
|
+
|
|
1115
|
+
if project_functions.event_type(behav["behavior"], ethogram) in cfg.STATE_EVENT_TYPES:
|
|
1116
|
+
if behav["duration"] not in ("-", cfg.NA) and categories[subject][category]["duration"] not in (
|
|
1117
|
+
"-",
|
|
1118
|
+
cfg.NA,
|
|
1119
|
+
):
|
|
1120
|
+
# print(f"{categories[subject][category]["duration"]=}")
|
|
1121
|
+
# print(f"{behav["duration"]=}")
|
|
1122
|
+
categories[subject][category]["duration"] += behav["duration"]
|
|
1123
|
+
else:
|
|
1124
|
+
categories[subject][category]["duration"] = cfg.NA
|
|
1125
|
+
|
|
1126
|
+
categories[subject][category]["number"] += behav["number"]
|
|
1127
|
+
|
|
1128
|
+
out_sorted: list = []
|
|
1129
|
+
for subject in parameters[cfg.SELECTED_SUBJECTS]:
|
|
1130
|
+
for behavior in parameters[cfg.SELECTED_BEHAVIORS]:
|
|
1131
|
+
for row in out:
|
|
1132
|
+
if row[cfg.SUBJECT] == subject and row["behavior"] == behavior:
|
|
1133
|
+
out_sorted.append(row)
|
|
1134
|
+
|
|
1135
|
+
# http://stackoverflow.com/questions/673867/python-arbitrary-order-by
|
|
1136
|
+
return (out_sorted, categories)
|