| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125 |
- import pandas as pd
- import numpy as np
- class DataParser:
- def __init__(self, dataframe: pd.DataFrame):
- self._df = dataframe
- def remap_columns(self, new_columns):
- self._df.rename(columns=new_columns, inplace=True)
- def remap_nans(self, new_nans):
- # Loop through the column names and their target float values
- for col, target_value in new_nans.items():
- # Check if the column exists in your DataFrame to prevent KeyErrors
- if col in self._df.columns:
- # 1. Convert the column to numeric type (strings/text will be coerced to NaN)
- self._df[col] = pd.to_numeric(self._df[col], errors='coerce')
- # 2. Create a safe float mask for just this column
- equal_to = np.isclose(
- self._df[col],
- target_value,
- atol=1e-8
- )
- # 3. Apply the mask and replace matching values with NaN in-place
- self._df.loc[equal_to, col] = np.nan
- def validate_and_filter_series_by_zeros(self, ser_col, pha_col, expected_phases):
- """
- Validates measurement series separated by zeros. If a series block between
- zeros does not contain the expected number of unique phases, the entire block is removed.
- Parameters:
- - ser_col (str): Column name for the series ID.
- - pha_col (str): Column name for the phase ID.
- - expected_phases (int): Required number of unique phases per series block (default: 6).
- """
- # 1. Identify rows that are zeros (separator rows)
- is_zero = (self._df[ser_col] == 0) & (self._df[pha_col] == 0)
- # 2. Create a unique group ID for each block of rows between zeros
- block_id = is_zero.cumsum()
- valid_indices = []
- # 3. Iterate through each block between zeros
- ser_no = 1
- for b_id, group in self._df.groupby(block_id):
- # Skip pure zero separator blocks (we can keep or drop them, usually they are separators)
- if is_zero.loc[group.index].all():
- continue
- # Check how many unique phases are in this specific block
- unique_phases = group[pha_col].nunique() - 1
- # If it matches the expected number of phases, keep its indices
- if unique_phases == expected_phases:
- self._df.loc[group.index, ser_col] = ser_no
- ser_no += 1
- valid_indices.extend(group.index.tolist())
- # 4. Keep only rows belonging to valid blocks
- invalid_indices = self._df.index.difference(valid_indices)
- self._df.drop(index=invalid_indices, inplace=True)
- def remove_specific_phases(self, pha_col, phases_to_remove):
- self._df.drop(
- index=self._df.index[
- self._df[pha_col].isin(phases_to_remove)
- ],
- inplace=True
- )
- def remap_phases(self, pha_col, phase_mapping):
- # Replace phase values according to the dictionary mapping
- if phase_mapping is not None:
- self._df[pha_col] = self._df[pha_col].replace(phase_mapping)
- def drop_rows_completely_null(self, columns):
- """
- Drops rows where ALL columns (except series and phase) are NaN simultaneously.
- """
- cols_to_check = [
- col for col in self._df.columns if col in columns
- ]
- self._df.dropna(
- subset=cols_to_check,
- how='all',
- inplace=True
- )
- def clean_precision_noise(self, precision=5):
- """
- Finds all float columns, rounds off float precision artifacts (e.g., 3.0000002 -> 3.0),
- and safely converts them using the 'g' specifier format so trailing zero decimals
- are completely dropped upon viewing or CSV export, while leaving real decimals intact.
- """
- # 1. Locate all float columns dynamically to avoid breaking string/text columns
- float_cols = self._df.select_dtypes(
- include=['float', 'float64']
- ).columns
- # 2. Round the target columns to snap the noisy precision bits to their actual values
- self._df[float_cols] = self._df[float_cols].round(precision)
- # 3. Format the cells to trim the trailing '.0' cleanly using g-formatting
- # (This is vectorized across selected fields and leaves NaNs unharmed)
- for col in float_cols:
- self._df[col] = self._df[col].map(
- lambda x: f"{x:g}" if pd.notnull(x) else x
- )
- def get_dataframe(self):
- return self._df
|