Save the emissions data from a whole run to a CSV file. * If the file does not exist, then create it. * If the file already exists but has invalid headers, then back it up and replace with new data. * If the file already exists and has valid headers: * I
(self, total: EmissionsData, _)
| 72 | return sorted(data.values.keys()) == list_of_column_names |
| 73 | |
| 74 | def out(self, total: EmissionsData, _): |
| 75 | """ |
| 76 | Save the emissions data from a whole run to a CSV file. |
| 77 | |
| 78 | * If the file does not exist, then create it. |
| 79 | * If the file already exists but has invalid headers, then back it up and replace with new data. |
| 80 | * If the file already exists and has valid headers: |
| 81 | * If it has no rows with a matching run ID, append the new data. |
| 82 | * If it has one row with a matching run ID, then replace that row with the new data. |
| 83 | * If it has > one row with a matching run ID, append the new data |
| 84 | |
| 85 | Args: |
| 86 | total: data to save. |
| 87 | |
| 88 | |
| 89 | """ |
| 90 | file_exists: bool = os.path.isfile(self.save_file_path) |
| 91 | if file_exists and os.path.getsize(self.save_file_path) == 0: |
| 92 | logger.warning( |
| 93 | f"File {self.save_file_path} exists but is empty. Treating as new file." |
| 94 | ) |
| 95 | file_exists = False |
| 96 | if file_exists and not self.has_valid_headers(total): |
| 97 | logger.warning("The CSV format has changed, backing up old emission file.") |
| 98 | backup(self.save_file_path) |
| 99 | file_exists = False |
| 100 | new_df = pd.DataFrame.from_records([dict(total.values)]) |
| 101 | if not file_exists: |
| 102 | df = new_df |
| 103 | elif self.on_csv_write == "append": |
| 104 | df = pd.read_csv(self.save_file_path) |
| 105 | # Filter out empty or all-NA columns only from new_df, to avoid warnings from Pandas, |
| 106 | # see https://github.com/pandas-dev/pandas/issues/55928 |
| 107 | new_df = new_df.dropna(axis=1, how="all") |
| 108 | df = pd.concat([df, new_df]) |
| 109 | else: |
| 110 | df = pd.read_csv(self.save_file_path) |
| 111 | df_run = df.loc[df.run_id == total.run_id] |
| 112 | if len(df_run) < 1: |
| 113 | df = pd.concat([df, new_df]) |
| 114 | elif len(df_run) > 1: |
| 115 | logger.warning( |
| 116 | f"CSV contains more than 1 ({len(df_run)})" |
| 117 | + f" rows with current run ID ({total.run_id})." |
| 118 | + "Appending instead of updating." |
| 119 | ) |
| 120 | df = pd.concat([df, new_df]) |
| 121 | else: |
| 122 | update_values = {} |
| 123 | for col, val in dict(total.values).items(): |
| 124 | # Explicitly cast new values to prevent warnings about incompatible dtypes. |
| 125 | update_values[col] = df[col].dtype.type(val) |
| 126 | df.loc[df.run_id == total.run_id, update_values.keys()] = ( |
| 127 | update_values.values() |
| 128 | ) |
| 129 | |
| 130 | df.to_csv(self.save_file_path, index=False) |
| 131 |