Skip to content

Pre-processing

Class for performing preprocessing on the loaded data.

Preprocessor dataclass

Class which implements generic functionality for pre-processing of sensor and meteorology information.

Attributes:

Name Type Description
time_bin_edges DatetimeArray

edges of the time bins to be used for smoothing/interpolation.

sensor_object SensorGroup

sensor group object containing raw data.

met_object Meteorology

met object containing raw data.

aggregate_function str

function to be used for aggregation of data. Defaults to mean.

sensor_fields list

standard list of sensor attributes that we wish to regularize and/or filter.

met_fields list

standard list of meteorology attributes that we wish to regularize/filter.

Source code in src/pyelq/preprocessing.py
 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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
@dataclass
class Preprocessor:
    """Class which implements generic functionality for pre-processing of sensor and meteorology information.

    Attributes:
        time_bin_edges (pd.arrays.DatetimeArray): edges of the time bins to be used for smoothing/interpolation.
        sensor_object (SensorGroup): sensor group object containing raw data.
        met_object (Meteorology): met object containing raw data.
        aggregate_function (str): function to be used for aggregation of data. Defaults to mean.
        sensor_fields (list): standard list of sensor attributes that we wish to regularize and/or filter.
        met_fields (list): standard list of meteorology attributes that we wish to regularize/filter.

    """

    time_bin_edges: pd.arrays.DatetimeArray
    sensor_object: SensorGroup
    met_object: Union[Meteorology, MeteorologyGroup]
    aggregate_function: str = "mean"
    sensor_fields = ["time", "concentration", "source_on"]
    met_fields = [
        "time",
        "wind_direction",
        "wind_speed",
        "pressure",
        "temperature",
        "u_component",
        "v_component",
        "w_component",
        "wind_turbulence_horizontal",
        "wind_turbulence_vertical",
    ]

    def __post_init__(self) -> None:
        """Initialise the class.

        Attaching the sensor and meteorology objects as attributes, and running initial regularization and NaN filtering
        steps.

        Before running the regularization & NaN filtering, the function ensures that u_component and v_component are
        present as fields on met_object. The post-smoothing wind speed and direction are then calculated from the
        smoothed u and v components, to eliminate the need to take means of directions when binning.

        The sensor and meteorology group objects attached to the class will have identical numbers of data points per
        device, identical time stamps, and be free of NaNs.

        """
        self.met_object.calculate_uv_from_wind_speed_direction()

        self.regularize_data()
        self.met_object.calculate_wind_direction_from_uv()
        self.met_object.calculate_wind_speed_from_uv()
        self.filter_nans()

    def regularize_data(self) -> None:
        """Smoothing or interpolation of data onto a common set of time points.

        Function which takes in sensor and meteorology objects containing raw data (on original time points), and
        smooths or interpolates these onto a common set of time points.

        When a SensorGroup object is supplied, the function will return a SensorGroup object with the same number of
        sensors. When a MeteorologyGroup object is supplied, the function will return a MeteorologyGroup object with the
        same number of objects. When a Meteorology object is supplied, the function will return a MeteorologyGroup
        object with the same number of objects as there is sensors in the SensorGroup object. The individual Meteorology
        objects will be identical.

        Assumes that sensor_object and met_object attributes contain the RAW data, on the original time stamps, as
        loaded from file/API using the relevant data access class.

        After the function has been run, the sensor and meteorology group objects attached to the class as attributes
        will have identical time stamps, but may still contain NaNs.

        """
        sensor_out = deepcopy(self.sensor_object)
        for sns_new, sns_old in zip(sensor_out.values(), self.sensor_object.values()):
            for field in self.sensor_fields:
                if (field != "time") and (getattr(sns_old, field) is not None):
                    time_out, resampled_values = temporal_resampling(
                        sns_old.time, getattr(sns_old, field), self.time_bin_edges, self.aggregate_function
                    )
                    setattr(sns_new, field, resampled_values)
            sns_new.time = time_out

        met_out = MeteorologyGroup()
        if isinstance(self.met_object, Meteorology):
            single_met_object = self.interpolate_single_met_object(met_in_object=self.met_object)
            for key in sensor_out.keys():
                met_out[key] = single_met_object
        else:
            for key, temp_met_object in self.met_object.items():
                met_out[key] = self.interpolate_single_met_object(met_in_object=temp_met_object)

        self.sensor_object = sensor_out
        self.met_object = met_out

    def filter_nans(self) -> None:
        """Filter out data points where any of the specified sensor or meteorology fields has a NaN value.

        Assumes that sensor_object and met_object attributes have first been passed through the regularize_data
        function, and thus have fields on aligned time grids.

        Function first works through all sensor and meteorology fields and finds indices of all times where there is a
        NaN value in any field. Then, it uses the resulting index to filter all fields.

        The result of this function is that the sensor_object and met_object attributes of the class are updated, any
        NaN values having been removed.

        """
        for sns_key, met_key in zip(self.sensor_object, self.met_object):
            sns_in = self.sensor_object[sns_key]
            met_in = self.met_object[met_key]
            filter_index = np.ones(sns_in.nof_observations, dtype=bool)
            for field in self.sensor_fields:
                if (field != "time") and (getattr(sns_in, field) is not None):
                    filter_index = np.logical_and(filter_index, np.logical_not(np.isnan(getattr(sns_in, field))))
            for field in self.met_fields:
                if (field != "time") and (getattr(met_in, field) is not None):
                    filter_index = np.logical_and(filter_index, np.logical_not(np.isnan(getattr(met_in, field))))

            self.sensor_object[sns_key] = self.filter_object_fields(sns_in, self.sensor_fields, filter_index)
            self.met_object[met_key] = self.filter_object_fields(met_in, self.met_fields, filter_index)

    def filter_on_met(self, filter_variable: list, lower_limit: list = None, upper_limit: list = None) -> None:
        """Filter the supplied data on given properties of the meteorological data.

        Assumes that the SensorGroup and MeteorologyGroup objects attached as attributes have corresponding values (one
        per sensor device), and have attributes that have been pre-smoothed/interpolated onto a common time grid per
        device.

        The result of this function is that the sensor_object and met_object attributes are updated with the filtered
        versions.

        Args:
            filter_variable (list of str): list of meteorology variables that we wish to use for filtering.
            lower_limit (list of float): list of lower limits associated with the variables in filter_variables.
                Defaults to None.
            upper_limit (list of float): list of upper limits associated with the variables in filter_variables.
                Defaults to None.

        """
        if lower_limit is None:
            lower_limit = [-np.infty] * len(filter_variable)
        if upper_limit is None:
            upper_limit = [np.infty] * len(filter_variable)

        for vrb, low, high in zip(filter_variable, lower_limit, upper_limit):
            for sns_key, met_key in zip(self.sensor_object, self.met_object):
                sns_in = self.sensor_object[sns_key]
                met_in = self.met_object[met_key]
                index_keep = np.logical_and(getattr(met_in, vrb) >= low, getattr(met_in, vrb) <= high)
                self.sensor_object[sns_key] = self.filter_object_fields(sns_in, self.sensor_fields, index_keep)
                self.met_object[met_key] = self.filter_object_fields(met_in, self.met_fields, index_keep)

    def block_data(
        self, time_edges: pd.arrays.DatetimeArray, data_object: Union[SensorGroup, MeteorologyGroup]
    ) -> list:
        """Break the supplied data group objects into time-blocked chunks.

        Returning a list of sensor and meteorology group objects per time chunk.

        If there is no data for a given device in a particular period, then that device is simply dropped from the group
        object in that block.

        Either a SensorGroup or a MeteorologyGroup object can be supplied, and the list of blocked objects returned will
        be of the same type.

        Args:
            time_edges (pd.Arrays.DatetimeArray): [(n_period + 1) x 1] array of edges of the time bins to be used for
                dividing the data into blocks.
            data_object (SensorGroup or MeteorologyGroup): data object containing either or meteorological data, to be
                divided into blocks.

        Returns:
            data_list (list): list of [n_period x 1] data objects, each list element being either a SensorGroup or
                MeteorologyGroup object (depending on the input) containing the data for the corresponding period.

        """
        data_list = []
        nof_periods = len(time_edges) - 1
        if isinstance(data_object, SensorGroup):
            field_list = self.sensor_fields
        elif isinstance(data_object, MeteorologyGroup):
            field_list = self.met_fields
        else:
            raise TypeError("Data input must be either a SensorGroup or MeteorologyGroup.")

        for k in range(nof_periods):
            data_list.append(type(data_object)())
            for key, dat in data_object.items():
                idx_time = (dat.time >= time_edges[k]) & (dat.time <= time_edges[k + 1])
                if np.any(idx_time):
                    data_list[-1][key] = deepcopy(dat)
                    data_list[-1][key] = self.filter_object_fields(data_list[-1][key], field_list, idx_time)
        return data_list

    @staticmethod
    def filter_object_fields(
        data_object: Union[Sensor, Meteorology], fields: list, index: np.ndarray
    ) -> Union[Sensor, Meteorology]:
        """Apply a filter index to all the fields in a given data object.

        Can be used for either a Sensor or Meteorology object.

        Args:
            data_object (Union[Sensor, Meteorology]): sensor or meteorology object (corresponding to a single device)
                for which fields are to be filtered.
            fields (list): list of field names to be filtered.
            index (np.ndarray): filter index.

        Returns:
            Union[Sensor, Meteorology]: filtered data object.

        """
        return_object = deepcopy(data_object)
        for field in fields:
            if getattr(return_object, field) is not None:
                setattr(return_object, field, getattr(return_object, field)[index])
        return return_object

    def interpolate_single_met_object(self, met_in_object: Meteorology) -> Meteorology:
        """Interpolate a single Meteorology object onto the time grid of the class.

        Args:
            met_in_object (Meteorology): Meteorology object to be interpolated onto the time grid of the class.

        Returns:
            met_out_object (Meteorology): interpolated Meteorology object.

        """
        met_out_object = Meteorology()
        time_out = None
        for field in self.met_fields:
            if (field != "time") and (getattr(met_in_object, field) is not None):
                time_out, resampled_values = temporal_resampling(
                    met_in_object.time,
                    getattr(met_in_object, field),
                    self.time_bin_edges,
                    self.aggregate_function,
                )
                setattr(met_out_object, field, resampled_values)

        if time_out is not None:
            met_out_object.time = time_out

        return met_out_object

__post_init__()

Initialise the class.

Attaching the sensor and meteorology objects as attributes, and running initial regularization and NaN filtering steps.

Before running the regularization & NaN filtering, the function ensures that u_component and v_component are present as fields on met_object. The post-smoothing wind speed and direction are then calculated from the smoothed u and v components, to eliminate the need to take means of directions when binning.

The sensor and meteorology group objects attached to the class will have identical numbers of data points per device, identical time stamps, and be free of NaNs.

Source code in src/pyelq/preprocessing.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def __post_init__(self) -> None:
    """Initialise the class.

    Attaching the sensor and meteorology objects as attributes, and running initial regularization and NaN filtering
    steps.

    Before running the regularization & NaN filtering, the function ensures that u_component and v_component are
    present as fields on met_object. The post-smoothing wind speed and direction are then calculated from the
    smoothed u and v components, to eliminate the need to take means of directions when binning.

    The sensor and meteorology group objects attached to the class will have identical numbers of data points per
    device, identical time stamps, and be free of NaNs.

    """
    self.met_object.calculate_uv_from_wind_speed_direction()

    self.regularize_data()
    self.met_object.calculate_wind_direction_from_uv()
    self.met_object.calculate_wind_speed_from_uv()
    self.filter_nans()

regularize_data()

Smoothing or interpolation of data onto a common set of time points.

Function which takes in sensor and meteorology objects containing raw data (on original time points), and smooths or interpolates these onto a common set of time points.

When a SensorGroup object is supplied, the function will return a SensorGroup object with the same number of sensors. When a MeteorologyGroup object is supplied, the function will return a MeteorologyGroup object with the same number of objects. When a Meteorology object is supplied, the function will return a MeteorologyGroup object with the same number of objects as there is sensors in the SensorGroup object. The individual Meteorology objects will be identical.

Assumes that sensor_object and met_object attributes contain the RAW data, on the original time stamps, as loaded from file/API using the relevant data access class.

After the function has been run, the sensor and meteorology group objects attached to the class as attributes will have identical time stamps, but may still contain NaNs.

Source code in src/pyelq/preprocessing.py
 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
def regularize_data(self) -> None:
    """Smoothing or interpolation of data onto a common set of time points.

    Function which takes in sensor and meteorology objects containing raw data (on original time points), and
    smooths or interpolates these onto a common set of time points.

    When a SensorGroup object is supplied, the function will return a SensorGroup object with the same number of
    sensors. When a MeteorologyGroup object is supplied, the function will return a MeteorologyGroup object with the
    same number of objects. When a Meteorology object is supplied, the function will return a MeteorologyGroup
    object with the same number of objects as there is sensors in the SensorGroup object. The individual Meteorology
    objects will be identical.

    Assumes that sensor_object and met_object attributes contain the RAW data, on the original time stamps, as
    loaded from file/API using the relevant data access class.

    After the function has been run, the sensor and meteorology group objects attached to the class as attributes
    will have identical time stamps, but may still contain NaNs.

    """
    sensor_out = deepcopy(self.sensor_object)
    for sns_new, sns_old in zip(sensor_out.values(), self.sensor_object.values()):
        for field in self.sensor_fields:
            if (field != "time") and (getattr(sns_old, field) is not None):
                time_out, resampled_values = temporal_resampling(
                    sns_old.time, getattr(sns_old, field), self.time_bin_edges, self.aggregate_function
                )
                setattr(sns_new, field, resampled_values)
        sns_new.time = time_out

    met_out = MeteorologyGroup()
    if isinstance(self.met_object, Meteorology):
        single_met_object = self.interpolate_single_met_object(met_in_object=self.met_object)
        for key in sensor_out.keys():
            met_out[key] = single_met_object
    else:
        for key, temp_met_object in self.met_object.items():
            met_out[key] = self.interpolate_single_met_object(met_in_object=temp_met_object)

    self.sensor_object = sensor_out
    self.met_object = met_out

filter_nans()

Filter out data points where any of the specified sensor or meteorology fields has a NaN value.

Assumes that sensor_object and met_object attributes have first been passed through the regularize_data function, and thus have fields on aligned time grids.

Function first works through all sensor and meteorology fields and finds indices of all times where there is a NaN value in any field. Then, it uses the resulting index to filter all fields.

The result of this function is that the sensor_object and met_object attributes of the class are updated, any NaN values having been removed.

Source code in src/pyelq/preprocessing.py
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
def filter_nans(self) -> None:
    """Filter out data points where any of the specified sensor or meteorology fields has a NaN value.

    Assumes that sensor_object and met_object attributes have first been passed through the regularize_data
    function, and thus have fields on aligned time grids.

    Function first works through all sensor and meteorology fields and finds indices of all times where there is a
    NaN value in any field. Then, it uses the resulting index to filter all fields.

    The result of this function is that the sensor_object and met_object attributes of the class are updated, any
    NaN values having been removed.

    """
    for sns_key, met_key in zip(self.sensor_object, self.met_object):
        sns_in = self.sensor_object[sns_key]
        met_in = self.met_object[met_key]
        filter_index = np.ones(sns_in.nof_observations, dtype=bool)
        for field in self.sensor_fields:
            if (field != "time") and (getattr(sns_in, field) is not None):
                filter_index = np.logical_and(filter_index, np.logical_not(np.isnan(getattr(sns_in, field))))
        for field in self.met_fields:
            if (field != "time") and (getattr(met_in, field) is not None):
                filter_index = np.logical_and(filter_index, np.logical_not(np.isnan(getattr(met_in, field))))

        self.sensor_object[sns_key] = self.filter_object_fields(sns_in, self.sensor_fields, filter_index)
        self.met_object[met_key] = self.filter_object_fields(met_in, self.met_fields, filter_index)

filter_on_met(filter_variable, lower_limit=None, upper_limit=None)

Filter the supplied data on given properties of the meteorological data.

Assumes that the SensorGroup and MeteorologyGroup objects attached as attributes have corresponding values (one per sensor device), and have attributes that have been pre-smoothed/interpolated onto a common time grid per device.

The result of this function is that the sensor_object and met_object attributes are updated with the filtered versions.

Parameters:

Name Type Description Default
filter_variable list of str

list of meteorology variables that we wish to use for filtering.

required
lower_limit list of float

list of lower limits associated with the variables in filter_variables. Defaults to None.

None
upper_limit list of float

list of upper limits associated with the variables in filter_variables. Defaults to None.

None
Source code in src/pyelq/preprocessing.py
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
def filter_on_met(self, filter_variable: list, lower_limit: list = None, upper_limit: list = None) -> None:
    """Filter the supplied data on given properties of the meteorological data.

    Assumes that the SensorGroup and MeteorologyGroup objects attached as attributes have corresponding values (one
    per sensor device), and have attributes that have been pre-smoothed/interpolated onto a common time grid per
    device.

    The result of this function is that the sensor_object and met_object attributes are updated with the filtered
    versions.

    Args:
        filter_variable (list of str): list of meteorology variables that we wish to use for filtering.
        lower_limit (list of float): list of lower limits associated with the variables in filter_variables.
            Defaults to None.
        upper_limit (list of float): list of upper limits associated with the variables in filter_variables.
            Defaults to None.

    """
    if lower_limit is None:
        lower_limit = [-np.infty] * len(filter_variable)
    if upper_limit is None:
        upper_limit = [np.infty] * len(filter_variable)

    for vrb, low, high in zip(filter_variable, lower_limit, upper_limit):
        for sns_key, met_key in zip(self.sensor_object, self.met_object):
            sns_in = self.sensor_object[sns_key]
            met_in = self.met_object[met_key]
            index_keep = np.logical_and(getattr(met_in, vrb) >= low, getattr(met_in, vrb) <= high)
            self.sensor_object[sns_key] = self.filter_object_fields(sns_in, self.sensor_fields, index_keep)
            self.met_object[met_key] = self.filter_object_fields(met_in, self.met_fields, index_keep)

block_data(time_edges, data_object)

Break the supplied data group objects into time-blocked chunks.

Returning a list of sensor and meteorology group objects per time chunk.

If there is no data for a given device in a particular period, then that device is simply dropped from the group object in that block.

Either a SensorGroup or a MeteorologyGroup object can be supplied, and the list of blocked objects returned will be of the same type.

Parameters:

Name Type Description Default
time_edges DatetimeArray

[(n_period + 1) x 1] array of edges of the time bins to be used for dividing the data into blocks.

required
data_object SensorGroup or MeteorologyGroup

data object containing either or meteorological data, to be divided into blocks.

required

Returns:

Name Type Description
data_list list

list of [n_period x 1] data objects, each list element being either a SensorGroup or MeteorologyGroup object (depending on the input) containing the data for the corresponding period.

Source code in src/pyelq/preprocessing.py
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
def block_data(
    self, time_edges: pd.arrays.DatetimeArray, data_object: Union[SensorGroup, MeteorologyGroup]
) -> list:
    """Break the supplied data group objects into time-blocked chunks.

    Returning a list of sensor and meteorology group objects per time chunk.

    If there is no data for a given device in a particular period, then that device is simply dropped from the group
    object in that block.

    Either a SensorGroup or a MeteorologyGroup object can be supplied, and the list of blocked objects returned will
    be of the same type.

    Args:
        time_edges (pd.Arrays.DatetimeArray): [(n_period + 1) x 1] array of edges of the time bins to be used for
            dividing the data into blocks.
        data_object (SensorGroup or MeteorologyGroup): data object containing either or meteorological data, to be
            divided into blocks.

    Returns:
        data_list (list): list of [n_period x 1] data objects, each list element being either a SensorGroup or
            MeteorologyGroup object (depending on the input) containing the data for the corresponding period.

    """
    data_list = []
    nof_periods = len(time_edges) - 1
    if isinstance(data_object, SensorGroup):
        field_list = self.sensor_fields
    elif isinstance(data_object, MeteorologyGroup):
        field_list = self.met_fields
    else:
        raise TypeError("Data input must be either a SensorGroup or MeteorologyGroup.")

    for k in range(nof_periods):
        data_list.append(type(data_object)())
        for key, dat in data_object.items():
            idx_time = (dat.time >= time_edges[k]) & (dat.time <= time_edges[k + 1])
            if np.any(idx_time):
                data_list[-1][key] = deepcopy(dat)
                data_list[-1][key] = self.filter_object_fields(data_list[-1][key], field_list, idx_time)
    return data_list

filter_object_fields(data_object, fields, index) staticmethod

Apply a filter index to all the fields in a given data object.

Can be used for either a Sensor or Meteorology object.

Parameters:

Name Type Description Default
data_object Union[Sensor, Meteorology]

sensor or meteorology object (corresponding to a single device) for which fields are to be filtered.

required
fields list

list of field names to be filtered.

required
index ndarray

filter index.

required

Returns:

Type Description
Union[Sensor, Meteorology]

Union[Sensor, Meteorology]: filtered data object.

Source code in src/pyelq/preprocessing.py
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
@staticmethod
def filter_object_fields(
    data_object: Union[Sensor, Meteorology], fields: list, index: np.ndarray
) -> Union[Sensor, Meteorology]:
    """Apply a filter index to all the fields in a given data object.

    Can be used for either a Sensor or Meteorology object.

    Args:
        data_object (Union[Sensor, Meteorology]): sensor or meteorology object (corresponding to a single device)
            for which fields are to be filtered.
        fields (list): list of field names to be filtered.
        index (np.ndarray): filter index.

    Returns:
        Union[Sensor, Meteorology]: filtered data object.

    """
    return_object = deepcopy(data_object)
    for field in fields:
        if getattr(return_object, field) is not None:
            setattr(return_object, field, getattr(return_object, field)[index])
    return return_object

interpolate_single_met_object(met_in_object)

Interpolate a single Meteorology object onto the time grid of the class.

Parameters:

Name Type Description Default
met_in_object Meteorology

Meteorology object to be interpolated onto the time grid of the class.

required

Returns:

Name Type Description
met_out_object Meteorology

interpolated Meteorology object.

Source code in src/pyelq/preprocessing.py
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
def interpolate_single_met_object(self, met_in_object: Meteorology) -> Meteorology:
    """Interpolate a single Meteorology object onto the time grid of the class.

    Args:
        met_in_object (Meteorology): Meteorology object to be interpolated onto the time grid of the class.

    Returns:
        met_out_object (Meteorology): interpolated Meteorology object.

    """
    met_out_object = Meteorology()
    time_out = None
    for field in self.met_fields:
        if (field != "time") and (getattr(met_in_object, field) is not None):
            time_out, resampled_values = temporal_resampling(
                met_in_object.time,
                getattr(met_in_object, field),
                self.time_bin_edges,
                self.aggregate_function,
            )
            setattr(met_out_object, field, resampled_values)

    if time_out is not None:
        met_out_object.time = time_out

    return met_out_object