4
\$\begingroup\$

Is there a better way to accomplish this? The function determines the Category for each row based on some conditions. Another set of conditions determines the ChangeGroup for the row. Not all the conditions are written but it more or less gets the point across. The determination for a ChangeGroup is made based on a previous row where ChangeGroup == 'BECMG'.

Data:

MOCK_DATA = [{'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-19 12:00:00', 'Ending': '2022-02-19 13:00:00', 'WindDirection': 200, 'WindSpeed': 7, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510111', 'Icing': None, 'Altimeter': 30.09, 'Temperature': -2}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-19 13:00:00', 'Ending': '2022-02-19 14:00:00', 'WindDirection': 200, 'WindSpeed': 8, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510111', 'Icing': None, 'Altimeter': 30.08, 'Temperature': -1}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-19 14:00:00', 'Ending': '2022-02-19 15:00:00', 'WindDirection': 210, 'WindSpeed': 10, 'WindGust': 15, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510111', 'Icing': None, 'Altimeter': 30.06, 'Temperature': 0}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-19 15:00:00', 'Ending': '2022-02-19 16:00:00', 'WindDirection': 220, 'WindSpeed': 11, 'WindGust': 20, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '520111', 'Icing': None, 'Altimeter': 30.02, 'Temperature': 2}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-19 16:00:00', 'Ending': '2022-02-19 17:00:00', 'WindDirection': 230, 'WindSpeed': 11, 'WindGust': 20, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'FEW', 'CloudBase1': 15500.0, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '520113', 'Icing': None, 'Altimeter': 30.01, 'Temperature': 4}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-19 17:00:00', 'Ending': '2022-02-19 18:00:00', 'WindDirection': 260, 'WindSpeed': 10, 'WindGust': 23, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SCT', 'CloudBase1': 4500.0, 'CloudCover2': 'BKN', 'CloudBase2': 6000.0, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510111', 'Icing': '620540', 'Altimeter': 30.01, 'Temperature': 6}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-19 18:00:00', 'Ending': '2022-02-19 19:00:00', 'WindDirection': 290, 'WindSpeed': 15, 'WindGust': 38, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'FEW', 'CloudBase1': 5000.0, 'CloudCover2': 'BKN', 'CloudBase2': 7500.0, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510118', 'Icing': '620621', 'Altimeter': 29.99, 'Temperature': 7}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-19 19:00:00', 'Ending': '2022-02-19 20:00:00', 'WindDirection': 290, 'WindSpeed': 18, 'WindGust': 39, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SCT', 'CloudBase1': 6000.0, 'CloudCover2': 'BKN', 'CloudBase2': 8500.0, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '520118', 'Icing': '620700', 'Altimeter': 30.0, 'Temperature': 7}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-19 20:00:00', 'Ending': '2022-02-19 21:00:00', 'WindDirection': 300, 'WindSpeed': 20, 'WindGust': 42, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '520111', 'Icing': None, 'Altimeter': 30.06, 'Temperature': 5}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-19 21:00:00', 'Ending': '2022-02-19 22:00:00', 'WindDirection': 310, 'WindSpeed': 18, 'WindGust': 40, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'FEW', 'CloudBase1': 6500.0, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '520128', 'Icing': None, 'Altimeter': 30.13, 'Temperature': 3}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-19 22:00:00', 'Ending': '2022-02-19 23:00:00', 'WindDirection': 310, 'WindSpeed': 17, 'WindGust': 39, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '520128', 'Icing': None, 'Altimeter': 30.18, 'Temperature': 1}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-19 23:00:00', 'Ending': '2022-02-20 00:00:00', 'WindDirection': 320, 'WindSpeed': 15, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '520121', 'Icing': None, 'Altimeter': 30.24, 'Temperature': 0}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-20 00:00:00', 'Ending': '2022-02-20 01:00:00', 'WindDirection': 320, 'WindSpeed': 13, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510130', 'Icing': None, 'Altimeter': 30.3, 'Temperature': -1}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-20 01:00:00', 'Ending': '2022-02-20 02:00:00', 'WindDirection': 320, 'WindSpeed': 13, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '520131', 'Icing': None, 'Altimeter': 30.34, 'Temperature': -2}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-20 02:00:00', 'Ending': '2022-02-20 03:00:00', 'WindDirection': 320, 'WindSpeed': 12, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510130', 'Icing': None, 'Altimeter': 30.38, 'Temperature': -3}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-20 03:00:00', 'Ending': '2022-02-20 04:00:00', 'WindDirection': 320, 'WindSpeed': 11, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False,'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510140', 'Icing': None, 'Altimeter': 30.4, 'Temperature': -3}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-20 04:00:00', 'Ending': '2022-02-20 05:00:00', 'WindDirection': 320, 'WindSpeed': 9, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510140', 'Icing': None, 'Altimeter': 30.42, 'Temperature': -4}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-20 05:00:00', 'Ending': '2022-02-20 06:00:00', 'WindDirection': 320, 'WindSpeed': 7, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510140', 'Icing': None, 'Altimeter': 30.44, 'Temperature': -4}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-20 06:00:00', 'Ending': '2022-02-20 07:00:00', 'WindDirection': 320, 'WindSpeed': 6, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510140', 'Icing': None, 'Altimeter': 30.45, 'Temperature': -4}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-20 07:00:00', 'Ending': '2022-02-20 08:00:00', 'WindDirection': 320, 'WindSpeed': 6, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510140', 'Icing': None, 'Altimeter': 30.46, 'Temperature': -4}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-20 08:00:00', 'Ending': '2022-02-20 09:00:00', 'WindDirection': 320, 'WindSpeed': 5, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510140', 'Icing': None, 'Altimeter': 30.45, 'Temperature': -4}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-20 09:00:00', 'Ending': '2022-02-20 10:00:00', 'WindDirection': 320, 'WindSpeed': 4, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': None, 'Altimeter': 30.45, 'Temperature': -4}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-20 10:00:00', 'Ending': '2022-02-20 11:00:00', 'WindDirection': 310, 'WindSpeed': 2, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': None, 'Altimeter': 30.46, 'Temperature': -4}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-20 11:00:00', 'Ending': '2022-02-20 12:00:00', 'WindDirection': 280, 'WindSpeed': 1, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': None, 'Altimeter': 30.48, 'Temperature': -4}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-20 12:00:00', 'Ending': '2022-02-20 13:00:00', 'WindDirection': 170, 'WindSpeed': 1, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': None, 'Altimeter': 30.49, 'Temperature': -4}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-20 13:00:00', 'Ending': '2022-02-20 14:00:00', 'WindDirection': 130, 'WindSpeed': 2, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': None, 'Altimeter': 30.5, 'Temperature': -4}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-20 14:00:00', 'Ending': '2022-02-20 15:00:00', 'WindDirection': 110, 'WindSpeed': 3, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': None, 'Altimeter': 30.5, 'Temperature': -3}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-20 15:00:00', 'Ending': '2022-02-20 16:00:00', 'WindDirection': 170, 'WindSpeed': 2, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': None, 'Altimeter': 30.52, 'Temperature': -2}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-20 16:00:00', 'Ending': '2022-02-20 17:00:00', 'WindDirection': 200, 'WindSpeed': 5, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': None, 'Altimeter': 30.51, 'Temperature': 0}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-20 17:00:00', 'Ending': '2022-02-20 18:00:00', 'WindDirection': 200, 'WindSpeed': 7, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': None, 'Altimeter': 30.49, 'Temperature': 1}, {'ChangeGroup': None, 'Category': None, 'Beginning': '2022-02-20 18:00:00', 'Ending': '2022-02-20 19:00:00', 'WindDirection': 190, 'WindSpeed': 8, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SKC', 'CloudBase1': None, 'CloudCover2': None, 'CloudBase2': None, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': None, 'Altimeter': 30.44, 'Temperature': 2}]

Data2

Another data with more changes to Category due to cloud cover. Sorry I'm working in slightly different environments. This set does not have the ChangeGroup and Category included

MOCK_DATA2 = [{'Beginning': '2022-02-03 06:00:00', 'Ending': '2022-02-03 07:00:00', 'WindDirection': 10, 'WindSpeed': 12, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'BKN', 'CloudBase1': 1500, 'CloudCover2': 'OVC', 'CloudBase2': 2100, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': '600197', 'Altimeter': 30.25, 'Temperature': -7}, {'Beginning': '2022-02-03 07:00:00', 'Ending': '2022-02-03 08:00:00', 'WindDirection': 10, 'WindSpeed': 13, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'BKN', 'CloudBase1': 800, 'CloudCover2': 'OVC', 'CloudBase2': 1500, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': '650138', 'Altimeter': 30.26, 'Temperature': -8}, {'Beginning': '2022-02-03 08:00:00', 'Ending': '2022-02-03 09:00:00', 'WindDirection': 0, 'WindSpeed': 12, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'BKN', 'CloudBase1': 800, 'CloudCover2': 'OVC', 'CloudBase2': 1500, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': '650138', 'Altimeter': 30.27, 'Temperature': -8}, {'Beginning': '2022-02-03 09:00:00', 'Ending': '2022-02-03 10:00:00', 'WindDirection': 10, 'WindSpeed': 13, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'BKN', 'CloudBase1': 800, 'CloudCover2': 'OVC', 'CloudBase2': 1500, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': '650138', 'Altimeter': 30.25, 'Temperature': -8}, {'Beginning': '2022-02-03 10:00:00', 'Ending': '2022-02-03 11:00:00', 'WindDirection': 0, 'WindSpeed': 14, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'BKN', 'CloudBase1': 800, 'CloudCover2': 'OVC', 'CloudBase2': 1500, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': '650138', 'Altimeter': 30.25, 'Temperature': -8}, {'Beginning': '2022-02-03 11:00:00', 'Ending': '2022-02-03 12:00:00', 'WindDirection': 10, 'WindSpeed': 13, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'BKN', 'CloudBase1': 800, 'CloudCover2': 'OVC', 'CloudBase2': 1500, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': '650138', 'Altimeter': 30.25, 'Temperature': -8}, {'Beginning': '2022-02-03 12:00:00', 'Ending': '2022-02-03 13:00:00', 'WindDirection': 10, 'WindSpeed': 12, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'BKN', 'CloudBase1': 800, 'CloudCover2': 'OVC', 'CloudBase2': 1500, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': '650138', 'Altimeter': 30.28, 'Temperature': -7}, {'Beginning': '2022-02-03 13:00:00', 'Ending': '2022-02-03 14:00:00', 'WindDirection': 10, 'WindSpeed': 13, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'BKN', 'CloudBase1': 800, 'CloudCover2': 'OVC', 'CloudBase2': 1500, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510131', 'Icing': '650138', 'Altimeter': 30.28, 'Temperature': -7}, {'Beginning': '2022-02-03 14:00:00', 'Ending': '2022-02-03 15:00:00', 'WindDirection': 20, 'WindSpeed': 13, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'BKN', 'CloudBase1': 800, 'CloudCover2': 'OVC', 'CloudBase2': 1500, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510131', 'Icing': '650138', 'Altimeter': 30.28, 'Temperature': -7}, {'Beginning': '2022-02-03 15:00:00', 'Ending': '2022-02-03 16:00:00', 'WindDirection': 10, 'WindSpeed': 13, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'BKN', 'CloudBase1': 800, 'CloudCover2': 'OVC', 'CloudBase2': 1500, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510131', 'Icing': '650138', 'Altimeter': 30.28, 'Temperature': -6}, {'Beginning': '2022-02-03 16:00:00', 'Ending': '2022-02-03 17:00:00', 'WindDirection': 20, 'WindSpeed': 13, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'BKN', 'CloudBase1': 1500, 'CloudCover2': 'OVC', 'CloudBase2': 2200, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510131', 'Icing': '650197', 'Altimeter': 30.27, 'Temperature': -6}, {'Beginning': '2022-02-03 17:00:00', 'Ending': '2022-02-03 18:00:00', 'WindDirection': 20, 'WindSpeed': 14, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'FEW', 'CloudBase1': 800, 'CloudCover2': 'BKN', 'CloudBase2': 1500, 'CloudCover3': 'OVC', 'CloudBase3': 2200.0, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510131', 'Icing': '650197', 'Altimeter': 30.26, 'Temperature': -5}, {'Beginning': '2022-02-03 18:00:00', 'Ending': '2022-02-03 19:00:00', 'WindDirection': 20, 'WindSpeed': 14, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'FEW', 'CloudBase1': 800, 'CloudCover2': 'BKN', 'CloudBase2': 1500, 'CloudCover3': 'OVC', 'CloudBase3': 2100.0, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': '650197', 'Altimeter': 30.24, 'Temperature': -5}, {'Beginning': '2022-02-03 19:00:00', 'Ending': '2022-02-03 20:00:00', 'WindDirection': 20, 'WindSpeed': 15, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'FEW', 'CloudBase1': 800, 'CloudCover2': 'BKN', 'CloudBase2': 1400, 'CloudCover3': 'OVC', 'CloudBase3': 2100.0, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510121', 'Icing': '650197', 'Altimeter': 30.22, 'Temperature': -5}, {'Beginning': '2022-02-03 20:00:00', 'Ending': '2022-02-03 21:00:00', 'WindDirection': 20, 'WindSpeed': 15, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'FEW', 'CloudBase1': 800, 'CloudCover2': 'BKN', 'CloudBase2': 1400, 'CloudCover3': 'OVC', 'CloudBase3': 2100.0, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510121', 'Icing': '650197', 'Altimeter': 30.21, 'Temperature': -5}, {'Beginning': '2022-02-03 21:00:00', 'Ending': '2022-02-03 22:00:00', 'WindDirection': 20, 'WindSpeed': 14, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'FEW', 'CloudBase1': 800, 'CloudCover2': 'BKN', 'CloudBase2': 1400, 'CloudCover3': 'OVC', 'CloudBase3': 2100.0, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510121', 'Icing': '650197', 'Altimeter': 30.22, 'Temperature': -5}, {'Beginning': '2022-02-03 22:00:00', 'Ending': '2022-02-03 23:00:00', 'WindDirection': 20, 'WindSpeed': 14, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'FEW', 'CloudBase1': 800, 'CloudCover2': 'BKN', 'CloudBase2': 1400, 'CloudCover3': 'OVC', 'CloudBase3': 2100.0, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510131', 'Icing': '650197', 'Altimeter': 30.23, 'Temperature': -5}, {'Beginning': '2022-02-03 23:00:00', 'Ending': '2022-02-04 00:00:00', 'WindDirection': 20, 'WindSpeed': 14, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'FEW', 'CloudBase1': 800, 'CloudCover2': 'BKN', 'CloudBase2': 1500, 'CloudCover3': 'OVC', 'CloudBase3': 2100.0, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510131', 'Icing': '650197', 'Altimeter': 30.24, 'Temperature': -5}, {'Beginning': '2022-02-04 00:00:00', 'Ending': '2022-02-04 01:00:00', 'WindDirection': 20, 'WindSpeed': 13, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'FEW', 'CloudBase1': 800, 'CloudCover2': 'BKN', 'CloudBase2': 1500, 'CloudCover3': 'OVC', 'CloudBase3': 2100.0, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510131', 'Icing': '620197', 'Altimeter': 30.26, 'Temperature': -5}, {'Beginning': '2022-02-04 01:00:00', 'Ending': '2022-02-04 02:00:00', 'WindDirection': 20, 'WindSpeed': 13, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'FEW', 'CloudBase1': 800, 'CloudCover2': 'BKN', 'CloudBase2': 1500, 'CloudCover3': 'OVC', 'CloudBase3': 2200.0, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510131', 'Icing': '620197', 'Altimeter': 30.27, 'Temperature': -5}, {'Beginning': '2022-02-04 02:00:00', 'Ending': '2022-02-04 03:00:00', 'WindDirection': 20, 'WindSpeed': 12, 'WindGust': None, 'Visibility': 9999, 'Precipitation': '-SN', 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'FEW', 'CloudBase1': 800, 'CloudCover2': 'BKN', 'CloudBase2': 1500, 'CloudCover3': 'OVC', 'CloudBase3': 2200.0, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510131', 'Icing': '620197', 'Altimeter': 30.27, 'Temperature': -6}, {'Beginning': '2022-02-04 03:00:00', 'Ending': '2022-02-04 04:00:00', 'WindDirection': 20, 'WindSpeed': 12, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'BKN', 'CloudBase1': 1500, 'CloudCover2': 'OVC', 'CloudBase2': 2200, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': '510131', 'Icing': '620207', 'Altimeter': 30.28, 'Temperature': -6}, {'Beginning': '2022-02-04 04:00:00', 'Ending': '2022-02-04 05:00:00', 'WindDirection': 10, 'WindSpeed': 11, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'BKN', 'CloudBase1': 1500, 'CloudCover2': 'OVC', 'CloudBase2': 2200, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': '620207', 'Altimeter': 30.29, 'Temperature': -6}, {'Beginning': '2022-02-04 05:00:00', 'Ending': '2022-02-04 06:00:00', 'WindDirection': 10, 'WindSpeed': 11, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'FEW', 'CloudBase1': 800, 'CloudCover2': 'BKN', 'CloudBase2': 1500, 'CloudCover3': 'OVC', 'CloudBase3': 2200.0, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': '620207', 'Altimeter': 30.29, 'Temperature': -6}, {'Beginning': '2022-02-04 06:00:00', 'Ending': '2022-02-04 07:00:00', 'WindDirection': 10, 'WindSpeed': 10, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'FEW', 'CloudBase1': 800, 'CloudCover2': 'BKN', 'CloudBase2': 1500, 'CloudCover3': 'OVC', 'CloudBase3': 2200.0, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': '620207', 'Altimeter': 30.28, 'Temperature': -7}, {'Beginning': '2022-02-04 07:00:00', 'Ending': '2022-02-04 08:00:00', 'WindDirection': 0, 'WindSpeed': 9, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SCT', 'CloudBase1': 800, 'CloudCover2': 'OVC', 'CloudBase2': 1500, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': '620207', 'Altimeter': 30.29, 'Temperature': -7}, {'Beginning': '2022-02-04 08:00:00', 'Ending': '2022-02-04 09:00:00', 'WindDirection': 350, 'WindSpeed': 10, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'BKN', 'CloudBase1': 900, 'CloudCover2': 'OVC', 'CloudBase2': 1500, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': '620138', 'Altimeter': 30.31, 'Temperature': -7}, {'Beginning': '2022-02-04 09:00:00', 'Ending': '2022-02-04 10:00:00', 'WindDirection': 350, 'WindSpeed': 10, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'BKN', 'CloudBase1': 900, 'CloudCover2': 'OVC', 'CloudBase2': 1500, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': '620138', 'Altimeter': 30.32, 'Temperature': -8}, {'Beginning': '2022-02-04 10:00:00', 'Ending': '2022-02-04 11:00:00', 'WindDirection': 350, 'WindSpeed': 9, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'BKN', 'CloudBase1': 900, 'CloudCover2': 'OVC', 'CloudBase2': 1500, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': '620138', 'Altimeter': 30.33, 'Temperature': -8}, {'Beginning': '2022-02-04 11:00:00', 'Ending': '2022-02-04 12:00:00', 'WindDirection': 340, 'WindSpeed': 8, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'BKN', 'CloudBase1': 900, 'CloudCover2': 'OVC', 'CloudBase2': 1500, 'CloudCover3': None, 'CloudBase3': None, 'CloudCover4': None, 'CloudBase4': None, 'CloudCover5': None, 'CloudBase5': None, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': '620138', 'Altimeter': 30.35, 'Temperature': -8}, {'Beginning': '2022-02-04 12:00:00', 'Ending': '2022-02-04 13:00:00', 'WindDirection': 350, 'WindSpeed': 8, 'WindGust': None, 'Visibility': 9999, 'Precipitation': None, 'Obscuration': None, 'Other': None, 'TSFlag': False, 'CloudCover1': 'SCT', 'CloudBase1': 900, 'CloudCover2': 'SCT', 'CloudBase2': 1500, 'CloudCover3': 'BKN', 'CloudBase3': 2900.0, 'CloudCover4': 'BKN', 'CloudBase4': 6500.0, 'CloudCover5': 'OVC', 'CloudBase5': 9000.0, 'WSHeight': None, 'WSDirection': None, 'WSSpeed': None, 'Turbulence': None, 'Icing': '620801', 'Altimeter': 30.35, 'Temperature': -9}]

Edit 1:

I've started to move some of the conditions out of the for loop and into more vectorized / list comprehension solutions

def dev_mock(taf: pd.DataFrame):
    """\
    applies the ammendment & specification criteria
    """
    ...
    taf.loc[0, 'ChangeGroup'] = 'BECMG'

    taf['MaxWind'] = taf[['WindSpeed', 'WindGust']].max(axis=1)

    cover, bases = taf[pd.Index(CLOUD_COVER)], taf[pd.Index(CLOUD_BASE)]

    cover = ((cover == 'BKN') | (cover == 'OVC'))

    cover.columns = bases.columns

    taf['MinCig'] = bases[cover].min(axis=1)

    taf['Category'] = [criteria.loc[
        (cig >= criteria['ceiling']) & (vis >= criteria['visibility'])
    ].index.max() for cig, vis in zip(taf.MinCig, taf.Visibility)]

   ...

Edit 2:

My attempt to implement the itertools.accumulate functools.reduce function. In the first instance I generate a new DataFrame as the accumulator from rows 0 & 1 and return that DataFrame as the accumulator.


    ...

    def accumulator_funk(acc, row) -> pd.DataFrame:

        if not isinstance(acc, pd.DataFrame):  # first instance

            df = pd.DataFrame(
                [acc, row]).set_index('Index')

            df.loc[row.Index, 'ChangeGroup'] = 'BECMG' if acc.Category != row.Category else None

            return df

        acc.loc[row.Index, 'Category'] = row.Category

        predom = acc.loc[acc.ChangeGroup == 'BECMG'].iloc[-1]

        acc.loc[row.Index, 'ChangeGroup'] = (
            'BECMG' if predom.Category != row.Category else None)

        return acc

    # dfs = itertools.accumulate(
    #    taf[['Category', 'ChangeGroup']].itertuples(), accumulator_funk)

    # fcst = ([df for df in dfs][-1])
    # more appropriate solution
    fcst = functools.reduce(
        accumulator_funk, taf[['Category', 'ChangeGroup']].itertuples())
    print(fcst)

Out: MOCK_DATA2

      Category ChangeGroup
Index                     
0            D       BECMG
1            C       BECMG
2            C        None
3            C        None
4            C        None
5            C        None
6            C        None
7            C        None
8            C        None
9            C        None
10           D       BECMG
11           D        None
12           D        None
13           D        None
14           D        None
15           D        None
16           D        None
17           D        None
18           D        None
19           D        None
20           D        None
21           D        None
22           D        None
23           D        None
24           D        None
25           D        None
26           C       BECMG
27           C        None
28           C        None
29           C        None
30           E       BECMG

Original Solution:

import pandas as pd
import numpy as np
CRITERIA = {
    'ceiling': [2000, 1000, 500, 200, 100],
    'visibility': [4800, 3200, 3200, 1600, 800],
    'index': ['E', 'D', 'C', 'B', 'A']
}
CLOUD_COVER = (
    'CloudCover1',
    'CloudCover2',
    'CloudCover3',
    'CloudCover4',
    'CloudCover5'
)
CLOUD_BASE = (
    'CloudBase1',
    'CloudBase2',
    'CloudBase3',
    'CloudBase4',
    'CloudBase5'
)
CLOUD_GROUP = np.stack((CLOUD_COVER, CLOUD_BASE), axis=1).flatten()

def dev_mock(taf: pd.DataFrame):
    """\
    applies the ammendment & specification criteria
    """

    criteria = pd.DataFrame(CRITERIA).set_index(
        'index').rename_axis('Category')

    def lowest_bkn_or_ovc_height(taf_row: pd.Series) -> int:
        ceiling = ((taf_row[pd.Index(CLOUD_COVER)] == 'OVC') | (
            taf_row[pd.Index(CLOUD_COVER)] == 'BKN'))

        if ceiling.any():
            return np.nanmin(np.where(
                ceiling, taf_row[pd.Index(CLOUD_BASE)], np.nan)
            )
        return 2000

    def max_wind(taf_series: pd.Series) -> int:
        return np.max(taf_series[['WindSpeed', 'WindGust']])

    for index, current_line in taf.iterrows():
        # minimum_flight_category
        taf.loc[index, 'Category'] = criteria.loc[
            (current_line['Visibility'] >= criteria['visibility']) &
            (lowest_bkn_or_ovc_height(current_line) >= criteria['ceiling'])
        ].index.max()
   

        if index == 0:
            taf.loc[index, 'ChangeGroup'] = 'BECMG'
            continue
        # -------------------------------------------------------
        predominate_line = (
            taf.loc[taf.loc[:, 'ChangeGroup'] == 'BECMG']).iloc[-1, ]

        predom_max_speed = max_wind(predominate_line)

        current_max_speed = max_wind(current_line)

        becoming_conditions = [
            ((np.abs(predom_max_speed - current_max_speed) >= 10) |
                # NOTE Wind Direction: A change > 30 degrees when the predominant
                # wind speed or gusts are expected to be 15 knots or greater
                ((np.max([predom_max_speed, current_max_speed]) > 15) & (
                    np.abs(predominate_line['WindDirection'] -
                           current_line['WindDirection']) > 30))) |
            # if the flight category has crossed a threshold
            (taf.loc[index, 'Category'] != predominate_line['Category'])
        ]

        taf.loc[index, 'ChangeGroup'] = (
            np.where(becoming_conditions, 'BECMG', None))

    return taf

if __name__ == '__main__':
    dev_mock(pd.DataFrame.from_records(MOCK_DATA))

\$\endgroup\$
4
  • \$\begingroup\$ I'm halfway through writing a suggestion but your category outputs have changed. They used to be all "E". Which is correct? \$\endgroup\$
    – Reinderien
    Feb 22, 2022 at 13:36
  • \$\begingroup\$ Uh yes sorry for the confusion, you are correct all the MOCK_DATA is cat E. I'm working from a couple different environments. The output of the update is from a different data source which I'll post as well. \$\endgroup\$ Feb 22, 2022 at 14:18
  • \$\begingroup\$ Do your ChangeGroup columns in the input always start off as None? \$\endgroup\$
    – Reinderien
    Feb 22, 2022 at 18:04
  • \$\begingroup\$ Yes. the purpose of the function is to determine and generate the change group. There is another change group TEMPO that I have yet to really start working on. A TEMPO condition is a condition which would only be expected to only occur for an intermittent amount of time and potentially several other factors. After this function, rows with ChangeGroup == None get dropped. Values df.fillna("") df.astype(str) formatted to the terminal aerodrome forecast specification. Then the entire dataframe is joined as a single string row\nrow\n.... \$\endgroup\$ Feb 22, 2022 at 18:16

1 Answer 1

1
\$\begingroup\$

Your second data set is more useful for testing since it outputs different categories instead of all E.

I am ignoring all of your edited code and am comparing for equivalence based on your original solution: aside from your "edit 2" which has a spelling mistake; funk should be func.

In your CRITERIA, rename index to category directly in the literal dictionary so that you don't have to do it later with rename_axis.

Delete your CLOUD_GROUP.

Delete the backslash in your docstring for dev_mock.

Your two inner functions - lowest_bkn_or_ovc_height and max_wind - should not be inner since they don't have any closure references; they should be moved to the global scope.

lowest_bkn_or_ovc_height needs to be rephrased in a vectorised manner. The comparison to OVC and BKN should be performed using an isin call. Don't call any. Apply your two-dimensional boolean predicate to a slice of the dataframe replacing ignored values with inf, and then apply min on axis 1. Take the default of 2000 using nan_to_num.

Your iterrows probably can't be avoided due to a convoluted, recursive reliance on a changing predominate_line (which should be called predominant_line).

Your becoming_conditions should not be in a list literal, and your subsequent application of np.where is incorrect. This is difficult or impossible to vectorise, so revert to a basic Python if. The binary operators |, & should be replaced with Python logical operators or, and because you're dealing with scalars.

In your becoming_conditions, do not call np.max with a list literal, nor np.abs, because in both cases there are only two values to compare so you should just use the Python built-ins.

The category assignment can be expressed in a vectorised manner by doing a cross-join (cartesian product) of the taf and criteria dataframes; filtering where the minimum predicates are true; doing a group by the original index; and then taking the maximum of each group.

Your dataframe should be loaded such that the timestamps are parsed from strings to actual timestamps. I have not shown this in my suggested code.

Spelling: ammendment -> amendment.

You should not, on every loop iteration, repeat your search for the last BECMG row. This can be stored in a variable that updates whenever a new BECMG assignment is performed.

Suggested

import pandas as pd
import numpy as np
from io import StringIO

MOCK_DATA = [...]
MOCK_DATA2 = [...]

CRITERIA = {
    'ceiling': [2000, 1000, 500, 200, 100],
    'visibility': [4800, 3200, 3200, 1600, 800],
    'category': ['E', 'D', 'C', 'B', 'A']
}
CLOUD_COVER = [
    'CloudCover1',
    'CloudCover2',
    'CloudCover3',
    'CloudCover4',
    'CloudCover5',
]
CLOUD_BASE = [
    'CloudBase1',
    'CloudBase2',
    'CloudBase3',
    'CloudBase4',
    'CloudBase5',
]


def lowest_bkn_or_ovc_height(taf: pd.DataFrame) -> np.ndarray:
    ceiling = taf[CLOUD_COVER].isin(('OVC', 'BKN'))

    # The cloud base types are screwed up and contain a bunch of None
    # that need to be coerced to floats.
    min_ceil = taf[CLOUD_BASE].astype(float).values
    min_ceil[~ceiling] = np.inf

    return np.nan_to_num(
        x=min_ceil.min(axis=1),
        posinf=2000,
    )


def get_category(taf: pd.DataFrame) -> pd.Series:
    taf = taf.assign(Index=taf.index)
    criteria = pd.DataFrame(CRITERIA)
    ceil_choice = (
        taf[['Index', 'Ceiling', 'Visibility']]
        .join(other=criteria, how='cross')
    )
    return (
        ceil_choice[
            (ceil_choice.Visibility >= ceil_choice.visibility) &
            (ceil_choice.Ceiling >= ceil_choice.ceiling)
        ]
        .groupby('Index').category.max()
    )


def dev_mock(taf: pd.DataFrame):
    """
    applies the amendment & specification criteria
    """

    taf['Ceiling'] = lowest_bkn_or_ovc_height(taf)
    taf['Category'] = get_category(taf)
    taf['MaxWind'] = taf[['WindSpeed', 'WindGust']].max(axis=1)

    taf.loc[0, 'ChangeGroup'] = 'BECMG'
    predominant_line = taf.loc[0]

    for index, current_line in taf.iloc[1:, :].iterrows():
        becoming_conditions = (
            abs(
                predominant_line.MaxWind - current_line.MaxWind
            ) >= 10
            # Wind Direction: A change > 30 degrees when the predominant
            # wind speed or gusts are expected to be 15 knots or greater
            or (
                max(predominant_line.MaxWind, current_line.MaxWind) > 15
                and abs(
                    predominant_line.WindDirection - current_line.WindDirection
                ) > 30
            )
            # if the flight category has crossed a threshold
            or current_line.Category != predominant_line.Category
        )

        if becoming_conditions:
            predominant_line = current_line
            group = 'BECMG'
        else:
            group = None
        taf.loc[index, 'ChangeGroup'] = group

    return taf


def main() -> None:
    df = pd.DataFrame.from_records(MOCK_DATA2)
    taf = dev_mock(df.copy())

    with StringIO() as f:
        taf.to_csv(f)
        print(f.getvalue())


if __name__ == '__main__':
    main()
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Thanks again for your input, it really helps me learn. Tomorrow I’ll set aside some time and break down your solution. You have a few methods that I haven’t really used before. \$\endgroup\$ Feb 23, 2022 at 0:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.