-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathenvironmental_analysis.py
More file actions
176 lines (141 loc) · 7.87 KB
/
Copy pathenvironmental_analysis.py
File metadata and controls
176 lines (141 loc) · 7.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
"""
Environmental factor analysis module for wildlife detection.
This module analyzes how environmental variables influence wildlife detection
success during camera operation periods. Unlike camera activity analysis, this
module focuses strictly on detection efficiency.
Environmental factors analyzed include:
- Tide gate positions
- Tidal depth levels
- Air temperature ranges
All analyses compute detection rates using a consistent and explicit animal
detection definition to avoid misclassification and ensure reproducibility.
"""
import pandas as pd
from scipy import stats
def _analyze_single_gate(df, gate_col, bins, labels):
"""
Helper function to analyze detection rates for a single gate column.
FIXED: Now uses proper animal detection logic and avoids SettingWithCopyWarning.
"""
if gate_col not in df.columns:
print(f"\nGate column '{gate_col}' not available for analysis.")
return None
# Handle string boolean values
df_temp = df.copy()
if df_temp['has_camera_data'].dtype == 'object':
df_temp['has_camera_data'] = df_temp['has_camera_data'] == 'True'
# Filter to camera observations only
camera_obs = df_temp[df_temp['has_camera_data']].copy()
if camera_obs.empty:
print(f"\nNo camera observations available for {gate_col} analysis.")
return None
# Create gate categories
camera_obs[f'{gate_col}_category'] = pd.cut(camera_obs[gate_col], bins=bins, labels=labels, right=False)
# Create proper animal detection flag
camera_obs['animal_detected'] = (
camera_obs['Species'].notna() &
(camera_obs['Notes'] != 'No animals detected')
).astype(int)
# Group by gate category and calculate detection rates
analysis_df = camera_obs.groupby(f'{gate_col}_category', observed=True).agg(
Total_Observations=('DateTime', 'count'),
Animal_Detections=('animal_detected', 'sum'),
Detection_Rate=('animal_detected', 'mean')
)
analysis_df['Detection_Rate_Pct'] = analysis_df['Detection_Rate'] * 100
print(f"\n--- Detection Rate by {gate_col} ---")
for idx, row in analysis_df.iterrows():
print(f"{idx}: {row['Animal_Detections']}/{row['Total_Observations']} ({row['Detection_Rate_Pct']:.1f}%)")
return analysis_df
def analyze_environmental_factors(combined_df):
"""
Analyzes and prints detection rates based on environmental factors.
FIXED: Now handles two separate gate analyses with correct animal detection logic and no warnings.
Returns:
tuple: Contains DataFrames for MTR gate, Hinge gate, tidal, and temp analysis.
"""
if combined_df.empty:
print("Combined DataFrame is empty. Cannot perform environmental analysis.")
return None, None, None, None
print("\n\n=== ENVIRONMENTAL ANALYSIS ===")
# --- 1. MTR Gate Analysis ---
mtr_bins = [-1, 5, 39, 63, 88]
mtr_labels = ['Closed (0-5°)', 'Partially Open (5-39°)', 'Open (39-63°)', 'Wide Open (>63°)']
mtr_gate_analysis = _analyze_single_gate(combined_df, 'Gate_Opening_MTR_Deg', mtr_bins, mtr_labels)
# --- 2. Top Hinge Gate Analysis ---
hinge_bins = [-2, 4, 20, 35, 42]
hinge_labels = ['Closed (-2-4°)', 'Partially Open (4-20°)', 'Open (20-35°)', 'Wide Open (>35°)']
hinge_gate_analysis = _analyze_single_gate(combined_df, 'Gate_Opening_Top_Hinge_Deg', hinge_bins, hinge_labels)
# --- 3. Tidal Level Analysis ---
tidal_analysis = None
if 'Depth' in combined_df.columns:
# Handle string boolean values
df_temp = combined_df.copy()
if df_temp['has_camera_data'].dtype == 'object':
df_temp['has_camera_data'] = df_temp['has_camera_data'] == 'True'
# Filter to camera observations only
camera_obs = df_temp[df_temp['has_camera_data']].copy()
if not camera_obs.empty:
# Calculate quantiles from camera observations with depth data
depth_data = camera_obs[camera_obs['Depth'].notna()].copy()
if not depth_data.empty:
quantiles = depth_data['Depth'].quantile([0.25, 0.75])
# Guard against degenerate (non-monotonic) quantile bin edges.
_cand = [depth_data['Depth'].min()-0.01, quantiles[0.25], quantiles[0.75], depth_data['Depth'].max()+0.01]
if all(pd.notna(v) for v in _cand) and all(_cand[i] < _cand[i+1] for i in range(len(_cand)-1)):
tide_level_categories = pd.cut(
depth_data['Depth'], bins=_cand,
labels=['Low Tide', 'Mid Tide', 'High Tide'])
else:
tide_level_categories = pd.Series(pd.NA, index=depth_data.index)
depth_data = depth_data.assign(tide_level=tide_level_categories)
# FIXED: Explicitly cast to int to avoid FutureWarning
animal_detected_values = (
depth_data['Species'].notna() &
(depth_data['Notes'] != 'No animals detected')
).astype(int)
depth_data = depth_data.assign(animal_detected=animal_detected_values)
tidal_analysis = depth_data.groupby('tide_level', observed=True).agg(
Total_Observations=('DateTime', 'count'),
Animal_Detections=('animal_detected', 'sum'),
Detection_Rate=('animal_detected', 'mean')
)
tidal_analysis['Detection_Rate_Pct'] = tidal_analysis['Detection_Rate'] * 100
print("\n--- Detection Rate by Tidal Level ---")
for idx, row in tidal_analysis.iterrows():
print(f"{idx}: {row['Animal_Detections']}/{row['Total_Observations']} ({row['Detection_Rate_Pct']:.1f}%)")
else:
print("\nTidal level (Depth) data not available for analysis.")
# --- 4. Weather Analysis ---
temp_analysis = None
if 'Air_Temp_C' in combined_df.columns:
# Handle string boolean values
df_temp = combined_df.copy()
if df_temp['has_camera_data'].dtype == 'object':
df_temp['has_camera_data'] = df_temp['has_camera_data'] == 'True'
# Filter to camera observations only
camera_obs = df_temp[df_temp['has_camera_data']].copy()
if not camera_obs.empty:
temp_data = camera_obs[camera_obs['Air_Temp_C'].notna()].copy()
if not temp_data.empty:
# FIXED: Explicitly create temp_bin categories to avoid FutureWarning
temp_bin_categories = pd.cut(temp_data['Air_Temp_C'], bins=5)
temp_data = temp_data.assign(temp_bin=temp_bin_categories)
# FIXED: Explicitly cast to int to avoid FutureWarning
animal_detected_values = (
temp_data['Species'].notna() &
(temp_data['Notes'] != 'No animals detected')
).astype(int)
temp_data = temp_data.assign(animal_detected=animal_detected_values)
temp_analysis = temp_data.groupby('temp_bin', observed=True).agg(
Total_Observations=('DateTime', 'count'),
Animal_Detections=('animal_detected', 'sum'),
Detection_Rate=('animal_detected', 'mean')
)
temp_analysis['Detection_Rate_Pct'] = temp_analysis['Detection_Rate'] * 100
print("\n--- Detection Rate by Air Temperature ---")
for idx, row in temp_analysis.iterrows():
print(f"{idx}: {row['Animal_Detections']}/{row['Total_Observations']} ({row['Detection_Rate_Pct']:.1f}%)")
else:
print("\nWeather (Air Temp) data not available for analysis.")
return mtr_gate_analysis, hinge_gate_analysis, tidal_analysis, temp_analysis