Skip to content
This repository was archived by the owner on Jun 7, 2022. It is now read-only.

Commit 59ce74d

Browse files
author
Emma Ferguson
committed
Add mult-filter to_dataframe method
1 parent 1e44fbc commit 59ce74d

File tree

2 files changed

+102
-17
lines changed

2 files changed

+102
-17
lines changed

purpleair/network.py

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -110,27 +110,12 @@ def filter_column(self,
110110
column: Optional[str],
111111
value_filter: Union[str, int, float, None]) -> pd.DataFrame:
112112
"""
113-
Filter sensors by column and value_filter. If only column is passed, we
114-
return rows that are not None. If the value_filter is passed, we only
115-
return rows where the column matches that value.
113+
Returns the output of filter_column_to_array as a pandas dataframe.
116114
"""
117115
# Check if there is no column passed
118116
if column is None:
119117
raise ValueError('No column name provided to filter on!')
120-
out_l: List[dict] = []
121-
for sensor in self.all_sensors:
122-
sensor_data = sensor.as_flat_dict(channel)
123-
if column not in sensor_data:
124-
raise ValueError(
125-
f'Requested column {column} does not exist in sensor data!')
126-
result = sensor_data.get(column)
127-
if value_filter and result != value_filter:
128-
continue
129-
if value_filter and result == value_filter:
130-
out_l.append(sensor_data)
131-
elif result is not None:
132-
# If we do not want to filter the values, we filter out `None`s
133-
out_l.append(sensor_data)
118+
out_l = filter_column_to_array(self.all_sensors, channel, column, value_filter)
134119

135120
if len(out_l) == 0:
136121
# pylint: disable=line-too-long
@@ -181,3 +166,63 @@ def to_dataframe(self,
181166

182167
sensor_data.index = sensor_data.pop('id')
183168
return sensor_data
169+
170+
def to_dataframe_multi_filter(self,
171+
channel: str,
172+
sensor_filters: Optional[str] = None,
173+
column: Optional[str] = None,
174+
value_filter: Union[str, int, float, None] = None) -> pd.DataFrame:
175+
"""
176+
Returns a Pandas dataframe with filtered sensors, based on multiple filters
177+
provided by the user. If no filters are provided, we return all sensors in
178+
self.all_sensors.
179+
"""
180+
181+
if not sensor_filters or len(sensor_filters) == 0 or 'all' in sensor_filters:
182+
sensor_data = pd.DataFrame([s.as_flat_dict(channel) for s in self.all_sensors])
183+
else:
184+
relevant_sensors = self.all_sensors
185+
186+
if 'outside' in sensor_filters:
187+
relevant_sensors = [s for s in relevant_sensors if s.location_type == 'outside']
188+
if 'useful' in sensor_filters:
189+
relevant_sensors = [s for s in relevant_sensors if s.is_useful()]
190+
if 'family' in sensor_filters:
191+
relevant_sensors = [s for s in relevant_sensors if s.parent and s.child]
192+
if 'no_child' in sensor_filters:
193+
relevant_sensors = [s for s in relevant_sensors if not s.child]
194+
195+
if 'column' in sensor_filters:
196+
relevant_sensors = filter_column_to_array(
197+
relevant_sensors, channel, column, value_filter)
198+
else:
199+
relevant_sensors = [s.as_flat_dict(channel) for s in relevant_sensors]
200+
sensor_data = pd.DataFrame(relevant_sensors)
201+
202+
sensor_data.index = sensor_data.pop('id')
203+
return sensor_data
204+
205+
def filter_column_to_array(
206+
sensors, channel: str, column: Optional[str],
207+
value_filter: Union[str, int, float, None]
208+
) -> List[dict]:
209+
"""
210+
Filter sensors by column and value_filter. If only column is passed, we
211+
return rows that are not None. If the value_filter is passed, we only
212+
return rows where the column matches that value.
213+
"""
214+
out_l: List[dict] = []
215+
for sensor in sensors:
216+
sensor_data = sensor.as_flat_dict(channel)
217+
if column not in sensor_data:
218+
raise ValueError(
219+
f'Requested column {column} does not exist in sensor data!')
220+
result = sensor_data.get(column)
221+
if value_filter and result != value_filter:
222+
continue
223+
if value_filter and result == value_filter:
224+
out_l.append(sensor_data)
225+
elif result is not None:
226+
# If we do not want to filter the values, we filter out `None`s
227+
out_l.append(sensor_data)
228+
return out_l

tests/test_purpleair.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,46 @@ def test_to_dataframe_cols(self):
6161
df_b = p.to_dataframe(sensor_filter='all', channel='child')
6262
self.assertListEqual(list(df_a.columns), list(df_b.columns))
6363

64+
def test_to_dataframe_multi_filter_outside_family(self):
65+
"""
66+
Test that family sensor plus outside sensor filter works.
67+
"""
68+
p = network.SensorList()
69+
p.to_dataframe_multi_filter(sensor_filters=['outside', 'family'], channel='parent')
70+
p.to_dataframe_multi_filter(sensor_filters=['outside', 'family'], channel='child')
71+
72+
def test_to_dataframe_multi_filter_parity(self):
73+
"""
74+
Test that to_dataframe_multi_filter returns same results as to_dataframe
75+
when just one filter is passed in.
76+
"""
77+
p = network.SensorList()
78+
79+
# all sensors
80+
df_single_filter = p.to_dataframe(sensor_filter='all', channel='parent')
81+
df_multi_filter = p.to_dataframe_multi_filter(sensor_filters=['all'], channel='parent')
82+
self.assertListEqual(list(df_single_filter.columns), list(df_multi_filter.columns))
83+
self.assertEqual(len(df_single_filter), len(df_multi_filter))
84+
85+
# outside sensors only
86+
df_single_filter = p.to_dataframe(sensor_filter='outside', channel='parent')
87+
df_multi_filter = p.to_dataframe_multi_filter(sensor_filters=['outside'], channel='parent')
88+
self.assertListEqual(list(df_single_filter.columns), list(df_multi_filter.columns))
89+
self.assertEqual(len(df_single_filter), len(df_multi_filter))
90+
91+
# no_child sensors only
92+
df_single_filter = p.to_dataframe(sensor_filter='no_child', channel='parent')
93+
df_multi_filter = p.to_dataframe_multi_filter(sensor_filters=['no_child'], channel='parent')
94+
self.assertListEqual(list(df_single_filter.columns), list(df_multi_filter.columns))
95+
self.assertEqual(len(df_single_filter), len(df_multi_filter))
96+
97+
# useful sensors only
98+
df_single_filter = p.to_dataframe(sensor_filter='useful', channel='parent')
99+
df_multi_filter = p.to_dataframe_multi_filter(sensor_filters=['useful'], channel='parent')
100+
self.assertListEqual(list(df_single_filter.columns), list(df_multi_filter.columns))
101+
self.assertEqual(len(df_single_filter), len(df_multi_filter))
102+
103+
64104

65105
class TestPurpleAirColumnFilters(unittest.TestCase):
66106
"""

0 commit comments

Comments
 (0)