Refactor: ML-Datenvorbereitung mit neuen Features & Klassen, Bugfixes
- Refactoring der Funktion `prepare_data_for_modeling`:
- Neue Bucket-Einteilung: Die Anzahl der Zielklassen (Techniker-Buckets) wurde zur Verbesserung der Robustheit von 7 auf 3 Klassen reduziert ('Klein', 'Mittel', 'Gross').
- Feature Engineering: Zusätzliche Features (`Umsatz_pro_MA`, `Log_Umsatz`, `Log_Mitarbeiter`) werden nun dynamisch erstellt und für das Training verwendet, um die Vorhersagekraft zu erhöhen.
- Feature-Auswahl: Die finalen Features für das Modelltraining wurden auf die neuen, transformierten numerischen Features umgestellt.
- Datenfilterung: Filter für DACH-Region und Plausibilität (Ausschluss von `FEHLER`-Fällen) wurden direkt in die Datenvorbereitung integriert.
- Bugfix: Ein `NameError` in `prepare_data_for_modeling` wurde behoben. Der Code zur Erstellung des 'is_part_of_group'-Features greift nun korrekt auf die Spalten des Pandas DataFrames statt auf eine nicht existierende `row_data`-Variable zu.
- Bugfix: Ein `SyntaxError` im `col_keys_mapping`-Dictionary wurde durch ein fehlendes Komma behoben.
- Code-Struktur: Der gesamte Datenverarbeitungsfluss innerhalb von `prepare_data_for_modeling` wurde für bessere Lesbarkeit und Konsistenz überarbeitet.
This commit is contained in:
@@ -9038,210 +9038,132 @@ class DataProcessor:
|
||||
|
||||
|
||||
|
||||
# --- Features konsolidieren (Umsatz, Mitarbeiter) ---
|
||||
# Nutzt die globale Hilfsfunktion get_numeric_filter_value (Block 5) - ERSETZT get_valid_numeric
|
||||
cols_to_process = {
|
||||
'Umsatz': ('umsatz_wiki', 'umsatz_crm', 'Finaler_Umsatz'),
|
||||
'Mitarbeiter': ('ma_wiki', 'ma_crm', 'Finaler_Mitarbeiter')
|
||||
# --- Features konsolidieren (Umsatz, Mitarbeiter) ---
|
||||
self.logger.debug("Konsolidiere Umsatz und Mitarbeiter für ML-Features...")
|
||||
cols_to_process_ml = {
|
||||
'Umsatz': ('umsatz_wiki', 'umsatz_crm', 'Finaler_Umsatz_ML'),
|
||||
'Mitarbeiter': ('ma_wiki', 'ma_crm', 'Finaler_Mitarbeiter_ML')
|
||||
}
|
||||
|
||||
for base_name, (wiki_col, crm_col, final_col) in cols_to_process.items():
|
||||
self.logger.debug(f"Verarbeite und konsolidiere '{base_name}' (Prioritaet: Wiki > CRM)...") # <<< GEÄNDERT
|
||||
# Sicherstellen, dass die Spalten im df_subset existieren, bevor apply aufgerufen wird.
|
||||
# Dies sollte durch die Spaltenauswahl oben garantiert sein, aber zur Sicherheit.
|
||||
wiki_series = df_subset[wiki_col].apply(lambda x: get_numeric_filter_value(x, is_umsatz=(base_name=='Umsatz'))) if wiki_col in df_subset.columns else pd.Series(np.nan, index=df_subset.index) # KORRIGIERT: Lambda hinzugefügt
|
||||
crm_series = df_subset[crm_col].apply(lambda x: get_numeric_filter_value(x, is_umsatz=(base_name=='Umsatz'))) if crm_col in df_subset.columns else pd.Series(np.nan, index=df_subset.index) # KORRIGIERT: Lambda hinzugefügt
|
||||
|
||||
|
||||
# np.where waehlt den Wiki-Wert, wenn er nicht 0/NaN ist, sonst den CRM-Wert.
|
||||
# WICHTIG: 0 ist hier das Kennzeichen fuer ungueltig/nicht parsebar/k.A. in get_numeric_filter_value
|
||||
df_subset[final_col] = np.where(
|
||||
(wiki_series.notna()) & (wiki_series > 0), # Wenn Wiki-Wert vorhanden UND > 0
|
||||
wiki_series, # Nimm den Wiki-Wert
|
||||
crm_series # Sonst nimm den CRM-Wert (der auch 0/NaN sein kann)
|
||||
for base_name, (wiki_col_ml, crm_col_ml, final_col_ml) in cols_to_process_ml.items():
|
||||
is_umsatz_flag = (base_name == 'Umsatz')
|
||||
wiki_series = df_subset[wiki_col_ml].apply(lambda x: get_numeric_filter_value(x, is_umsatz=is_umsatz_flag))
|
||||
crm_series = df_subset[crm_col_ml].apply(lambda x: get_numeric_filter_value(x, is_umsatz=is_umsatz_flag))
|
||||
|
||||
# Wähle Wiki-Wert, wenn vorhanden und > 0, sonst CRM-Wert
|
||||
df_subset.loc[:, final_col_ml] = np.where(
|
||||
(wiki_series.notna()) & (wiki_series > 0),
|
||||
wiki_series,
|
||||
crm_series
|
||||
)
|
||||
# Info-Log ueber Ergebnis
|
||||
self.logger.info(f" -> {df_subset[final_col].notna().sum()} gueltige '{final_col}' Werte erstellt (von {len(df_subset)} Zeilen).") # <<< GEÄNDERT
|
||||
# Ersetze 0 explizit durch NaN, damit es von log1p und Imputer korrekt behandelt wird
|
||||
df_subset.loc[:, final_col_ml] = df_subset[final_col_ml].replace(0, np.nan)
|
||||
self.logger.info(f" -> {df_subset[final_col_ml].notna().sum()} gueltige '{final_col_ml}' Werte erstellt (von {len(df_subset)} Zeilen).")
|
||||
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# +++ NEUER BLOCK: Feature Engineering (Ratio & Log-Transformationen) +++++
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
self.logger.info("Erstelle zusätzliche Features (Ratio, Log-Transformationen)...")
|
||||
|
||||
if 'Finaler_Umsatz_ML' in df_subset.columns and 'Finaler_Mitarbeiter_ML' in df_subset.columns:
|
||||
# Umsatz pro Mitarbeiter
|
||||
ma_for_ratio = df_subset['Finaler_Mitarbeiter_ML'] # Hier sind Nullen schon durch NaN ersetzt
|
||||
df_subset.loc[:, 'Umsatz_pro_MA_ML'] = df_subset['Finaler_Umsatz_ML'] / ma_for_ratio
|
||||
df_subset['Umsatz_pro_MA_ML'].replace([np.inf, -np.inf], np.nan, inplace=True)
|
||||
self.logger.debug(f" -> Feature 'Umsatz_pro_MA_ML' erstellt.")
|
||||
|
||||
# Log-Transformationen (np.log1p(x) berechnet log(1+x), sicher für NaNs)
|
||||
df_subset.loc[:, 'Log_Finaler_Umsatz_ML'] = np.log1p(df_subset['Finaler_Umsatz_ML'])
|
||||
df_subset.loc[:, 'Log_Finaler_Mitarbeiter_ML'] = np.log1p(df_subset['Finaler_Mitarbeiter_ML'])
|
||||
self.logger.debug(f" -> Log-transformierte Features erstellt.")
|
||||
else:
|
||||
self.logger.warning("Konsolidierte Umsatz/Mitarbeiter-Spalten nicht gefunden, Feature Engineering übersprungen.")
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# +++ ENDE NEUER BLOCK ++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
# --- Zielvariable vorbereiten (Technikerzahl) ---
|
||||
techniker_col_internal = "techniker" # Interne Spaltenname nach Umbenennung (aus col_keys_mapping)
|
||||
self.logger.info(f"Verarbeite Zielvariable '{techniker_col_internal}'...") # <<< GEÄNDERT
|
||||
|
||||
# Sicherstellen, dass die Spalte existiert
|
||||
if techniker_col_internal not in df_subset.columns:
|
||||
self.logger.critical(f"FEHLER: Zielvariable '{techniker_col_internal}' (CRM Anzahl Techniker) nicht im DataFrame gefunden nach Umbenennung.") # <<< GEÄNDERT
|
||||
return None # Beende die Methode
|
||||
|
||||
# Konvertiere zu Numerisch (Float/Int oder NaN) mit get_numeric_filter_value (Block 5).
|
||||
# Dies stellt sicher, dass nur gueltige, positive Zahlen verwendet werden.
|
||||
df_subset['Anzahl_Servicetechniker_Numeric'] = df_subset[techniker_col_internal].apply(lambda x: get_numeric_filter_value(x, is_umsatz=False)) # KORRIGIERT: Lambda hinzugefügt
|
||||
|
||||
|
||||
# Filtere Zeilen: Behalte nur die mit gueltiger, positiver Technikerzahl (Float > 0).
|
||||
initial_rows = len(df_subset)
|
||||
# Hier filtern wir basierend auf der numerischen Spalte, die durch get_numeric_filter_value erstellt wurde.
|
||||
df_filtered = df_subset[
|
||||
df_subset['Anzahl_Servicetechniker_Numeric'].notna() & # Nicht NaN
|
||||
(df_subset['Anzahl_Servicetechniker_Numeric'] > 0) # Und groesser als 0
|
||||
].copy() # WICHTIG: .copy() um SettingWithCopyWarning zu vermeiden
|
||||
filtered_rows = len(df_filtered)
|
||||
removed_rows = initial_rows - filtered_rows
|
||||
|
||||
# Info, wenn Zeilen entfernt wurden
|
||||
if removed_rows > 0:
|
||||
self.logger.info(f"{removed_rows} Zeilen entfernt aufgrund fehlender/ungueltiger Technikerzahl (Wert <= 0 oder nicht numerisch/parsebar).") # <<< GEÄNDERT
|
||||
self.logger.info(f"Verbleibende Zeilen fuer Modellierungstraining (mit gueltiger Technikerzahl > 0): {filtered_rows}") # <<< GEÄNDERT
|
||||
|
||||
# Wenn keine Zeilen uebrig bleiben, kann kein Modell trainiert werden.
|
||||
if filtered_rows == 0:
|
||||
self.logger.error("FEHLER: Keine Zeilen mit gueltiger Technikerzahl (>0) uebrig fuer Modellierungstraining!") # <<< GEÄNDERT
|
||||
return None # Beende die Methode
|
||||
|
||||
|
||||
# --- Techniker-Buckets erstellen ---
|
||||
# Die Bins und Labels muessen die gefilterten Daten widerspiegeln (die jetzt alle > 0 sind).
|
||||
# Die Bin-Definition muss so sein, dass alle Werte > 0 einem Bucket zugeordnet werden.
|
||||
# Beispiel: (-1, 0] -> Bucket 1 (0), (0, 19] -> Bucket 2 (<20), (19, 49] -> Bucket 3 (<50) etc.
|
||||
# Da wir auf >0 filtern, landet 0 nie im Trainingsset, aber die Bin-Definition muss trotzdem Sinn ergeben.
|
||||
# Alter Code:
|
||||
# bins = [-1, 0, 19, 49, 99, 249, 499, float('inf')]
|
||||
# labels = ['Bucket_1_(0)', 'Bucket_2_(<20)', 'Bucket_3_(<50)', 'Bucket_4_(<100)', 'Bucket_5_(<250)', 'Bucket_6_(<500)', 'Bucket_7_(>499)']
|
||||
self.logger.info("Verarbeite Zielvariable 'techniker'...")
|
||||
techniker_col_internal = "techniker"
|
||||
df_subset.loc[:, 'Anzahl_Servicetechniker_Numeric'] = df_subset[techniker_col_internal].apply(lambda x: get_numeric_filter_value(x, is_umsatz=False))
|
||||
|
||||
# NEUER VORSCHLAG (z.B. 3 Klassen):
|
||||
bins_new = [-1, 49, 249, float('inf')] # Grenzen: (<=49), (50-249), (>=250)
|
||||
initial_rows_before_tech_filter = len(df_subset)
|
||||
df_filtered = df_subset[
|
||||
df_subset['Anzahl_Servicetechniker_Numeric'].notna() &
|
||||
(df_subset['Anzahl_Servicetechniker_Numeric'] > 0)
|
||||
].copy()
|
||||
|
||||
removed_rows_tech_filter = initial_rows_before_tech_filter - len(df_filtered)
|
||||
if removed_rows_tech_filter > 0:
|
||||
self.logger.info(f"{removed_rows_tech_filter} Zeilen entfernt aufgrund fehlender/ungueltiger Technikerzahl.")
|
||||
self.logger.info(f"Verbleibende Zeilen fuer Modellierungstraining: {len(df_filtered)}")
|
||||
|
||||
if df_filtered.empty:
|
||||
self.logger.error("FEHLER: Keine Zeilen mit gueltiger Technikerzahl (>0) uebrig fuer Modellierungstraining!")
|
||||
return None
|
||||
|
||||
# --- Techniker-Buckets erstellen (mit reduzierter Klassenanzahl) ---
|
||||
self.logger.info("Erstelle reduzierte Techniker-Buckets (3 Klassen)...")
|
||||
bins_new = [-1, 49, 249, float('inf')]
|
||||
labels_new = ['Techniker_Klein (0-49)', 'Techniker_Mittel (50-249)', 'Techniker_Gross (250+)']
|
||||
|
||||
try:
|
||||
# Erstellen Sie die Bucket-Spalte mit pd.cut
|
||||
df_filtered['Techniker_Bucket'] = pd.cut(
|
||||
df_filtered['Anzahl_Servicetechniker_Numeric'], # Die numerische Technikerzahl-Spalte
|
||||
bins=bins, # Die definierten Grenzen
|
||||
labels=labels, # Die definierten Labels
|
||||
right=True, # Intervalle sind (links, rechts]. z.B. (0, 19] inkludiert 19.
|
||||
include_lowest=True # Inkludiert den niedrigsten Wert der ersten Bin (-1) (relevant, falls 0 moeglich waere)
|
||||
)
|
||||
self.logger.info("Techniker-Buckets erstellt.") # <<< GEÄNDERT
|
||||
|
||||
# Pruefe, ob NaNs in Buckets erstellt wurden (sollte bei >0 Filterung und korrekten Bins nicht passieren).
|
||||
if df_filtered['Techniker_Bucket'].isna().any():
|
||||
nan_bucket_rows = df_filtered['Techniker_Bucket'].isna().sum()
|
||||
self.logger.warning(f"WARNUNG: {nan_bucket_rows} Zeilen mit NaNs in Techniker-Buckets nach pd.cut erstellt. Ueberpruefen Sie die bins/labels oder die Filterung.") # <<< GEÄNDERT
|
||||
# Entfernen Sie diese Zeilen, da sie nicht zum Trainieren verwendet werden koennen.
|
||||
df_filtered.dropna(subset=['Techniker_Bucket'], inplace=True) # Entferne Zeilen mit NaN im Bucket
|
||||
self.logger.info(f"Nach Entfernung von {nan_bucket_rows} Zeilen mit NaN Buckets: {len(df_filtered)} Zeilen verbleiben fuer Training.") # <<< GEÄNDERT
|
||||
# Wenn nach Entfernung keine Zeilen mehr uebrig sind
|
||||
if len(df_filtered) == 0:
|
||||
self.logger.error("FEHLER: Keine Zeilen uebrig nach Entfernung von NaN Buckets. Modell kann nicht trainiert werden.") # <<< GEÄNDERT
|
||||
return None # Beende die Methode
|
||||
|
||||
|
||||
# Verteilung der Buckets als Info-Log (absolute Haeufigkeit und Prozent)
|
||||
self.logger.info(f"Verteilung der Techniker-Buckets im Trainingsdatensatz ({len(df_filtered)} Zeilen):\n{df_filtered['Techniker_Bucket'].value_counts(normalize=False).sort_index()}") # <<< GEÄNDERT
|
||||
self.logger.info(f"Verteilung (Prozent):\n{df_filtered['Techniker_Bucket'].value_counts(normalize=True).sort_index().round(3)}") # <<< GEÄNDERT
|
||||
|
||||
except Exception as e:
|
||||
# Fange Fehler beim Erstellen der Buckets ab
|
||||
self.logger.critical(f"FEHLER beim Erstellen der Techniker-Buckets: {e}") # <<< GEÄNDERT
|
||||
# Logge den Traceback
|
||||
self.logger.debug(traceback.format_exc()) # <<< GEÄNDERT
|
||||
return None # Beende die Methode
|
||||
|
||||
|
||||
|
||||
df_filtered.loc[:, 'Techniker_Bucket'] = pd.cut(
|
||||
df_filtered['Anzahl_Servicetechniker_Numeric'], bins=bins_new, labels=labels_new, right=True, include_lowest=True
|
||||
)
|
||||
self.logger.info(f"Verteilung der neuen Techniker-Buckets:\n{df_filtered['Techniker_Bucket'].value_counts(normalize=True, dropna=False).sort_index().round(3)}")
|
||||
|
||||
# --- Kategoriale Features vorbereiten (Branche) ---
|
||||
branche_col_internal = "branche_crm" # Interne Spaltenname nach Umbenennung (aus col_keys_mapping)
|
||||
self.logger.info(f"Verarbeite kategoriales Feature '{branche_col_internal}' fuer One-Hot Encoding...") # <<< GEÄNDERT
|
||||
|
||||
# Sicherstellen, dass die Spalte existiert
|
||||
if branche_col_internal not in df_filtered.columns:
|
||||
self.logger.critical(f"FEHLER: Spalte '{branche_col_internal}' nicht im DataFrame fuer One-Hot Encoding gefunden.") # <<< GEÄNDERT
|
||||
return None # Beende die Methode
|
||||
|
||||
# Stelle sicher, dass die Spalte String ist und fülle evtl. NaNs mit 'Unbekannt'.
|
||||
# .str.strip() entfernt führende/endende Leerzeichen.
|
||||
df_filtered[branche_col_internal] = df_filtered[branche_col_internal].astype(str).fillna('Unbekannt').str.strip()
|
||||
|
||||
|
||||
# One-Hot Encoding (pd.get_dummies)
|
||||
# dummy_na=False, da wir NaNs bereits mit 'Unbekannt' gefuellt haben.
|
||||
# prefix='Branche' ist gut, um die neuen Spalten zu identifizieren.
|
||||
self.logger.info("Verarbeite kategoriales Feature 'branche_crm' fuer One-Hot Encoding...")
|
||||
branche_col_internal = "branche_crm"
|
||||
df_filtered.loc[:, 'branche_crm'] = df_filtered['branche_crm'].astype(str).fillna('Unbekannt').str.strip()
|
||||
df_encoded = pd.get_dummies(df_filtered, columns=[branche_col_internal], prefix='Branche', dummy_na=False)
|
||||
self.logger.info(f"One-Hot Encoding fuer '{branche_col_internal}' durchgefuehrt. Neue Spaltenanzahl: {len(df_encoded.columns)}") # <<< GEÄNDERT
|
||||
|
||||
|
||||
|
||||
# --- Finale Auswahl der Features fuer das Modell ---
|
||||
# Identifizieren Sie die Feature-Spalten nach dem Encoding.
|
||||
# Dies sind alle Spalten, die mit 'Branche_' beginnen (One-Hot), plus die konsolidierten numerischen.
|
||||
feature_columns = [col for col in df_encoded.columns if col.startswith('Branche_')] # Alle One-Hot Branch-Spalten
|
||||
# Fuegen Sie die konsolidierten numerischen Spalten hinzu.
|
||||
feature_columns.extend(['Finaler_Umsatz', 'Finaler_Mitarbeiter'])
|
||||
feature_columns_ml = [col for col in df_encoded.columns if col.startswith('Branche_')]
|
||||
feature_columns_ml.extend([
|
||||
'Log_Finaler_Umsatz_ML',
|
||||
'Log_Finaler_Mitarbeiter_ML',
|
||||
'Umsatz_pro_MA_ML',
|
||||
'is_part_of_group'
|
||||
])
|
||||
self.logger.info(f"Finale Feature-Auswahl für das Training: {feature_columns_ml}")
|
||||
|
||||
target_column_ml = 'Techniker_Bucket'
|
||||
identification_cols_ml = ['name', 'Anzahl_Servicetechniker_Numeric']
|
||||
|
||||
final_cols_for_df_ml = identification_cols_ml + feature_columns_ml + [target_column_ml]
|
||||
missing_final_cols_ml = [col for col in final_cols_for_df_ml if col not in df_encoded.columns]
|
||||
if missing_final_cols_ml:
|
||||
self.logger.critical(f"FEHLER: Finale Spalten fuer Modellierung fehlen im DataFrame: {missing_final_cols_ml}")
|
||||
return None
|
||||
|
||||
# Pruefen Sie, ob die konsolidierten numerischen Spalten ('Finaler_Umsatz', 'Finaler_Mitarbeiter')
|
||||
# tatsaechlich im DataFrame df_encoded vorhanden sind (sollten sie, wurden oben erstellt).
|
||||
if not all(col in df_encoded.columns for col in ['Finaler_Umsatz', 'Finaler_Mitarbeiter']):
|
||||
self.logger.critical("FEHLER: Konsolidierte numerische Spalten 'Finaler_Umsatz' oder 'Finaler_Mitarbeiter' fehlen im DataFrame nach Konsolidierung.") # <<< GEÄNDERT
|
||||
return None # Beende die Methode
|
||||
df_model_ready = df_encoded[final_cols_for_df_ml].copy()
|
||||
|
||||
|
||||
target_column = 'Techniker_Bucket' # Die Zielvariable
|
||||
|
||||
|
||||
# Erstellen Sie den finalen DataFrame nur mit Features und Target (und Identifikationsspalten).
|
||||
# Behalten Sie 'name' und 'Anzahl_Servicetechniker_Numeric' fuer Reporting/Debugging.
|
||||
# 'name' ist der interne Name nach Umbenennung (aus col_keys_mapping).
|
||||
identification_cols = ['name', 'Anzahl_Servicetechniker_Numeric']
|
||||
# Sicherstellen, dass diese Identifikationsspalten auch im DataFrame existieren.
|
||||
if not all(col in df_encoded.columns for col in identification_cols):
|
||||
self.logger.critical(f"FEHLER: Identifikationsspalten {identification_cols} fehlen im DataFrame.") # <<< GEÄNDERT
|
||||
return None # Beende die Methode
|
||||
|
||||
|
||||
# Erstellen Sie die Liste der finalen Spalten fuer den DataFrame
|
||||
# Stellen Sie sicher, dass alle Feature-Spalten und die Zielspalte auch wirklich im DataFrame sind
|
||||
# (Koennte fehlen, wenn z.B. Finaler_Umsatz/Mitarbeiter oben fehlschlug und als NaN resultierte, was aber ok ist fuer Imputer).
|
||||
final_cols_for_df = identification_cols + feature_columns + [target_column]
|
||||
missing_final_cols = [col for col in final_cols_for_df if col not in df_encoded.columns]
|
||||
if missing_final_cols:
|
||||
self.logger.critical(f"FEHLER: Finale Spalten fuer Modellierung fehlen im DataFrame: {missing_final_cols}") # <<< GEÄNDERT
|
||||
return None # Beende die Methode
|
||||
|
||||
|
||||
# Erstellen Sie den finalen DataFrame mit den ausgewaehlten Spalten.
|
||||
df_model_ready = df_encoded[final_cols_for_df].copy() # Erstelle eine Kopie
|
||||
|
||||
|
||||
# Optional: Konvertieren Sie numerische Spalten explizit zu Float64.
|
||||
# Dies stellt sicher, dass der Imputer korrekt arbeitet.
|
||||
for col in ['Finaler_Umsatz', 'Finaler_Mitarbeiter', 'Anzahl_Servicetechniker_Numeric']:
|
||||
if col in df_model_ready.columns: # Sicherheitscheck, ob Spalte existiert
|
||||
# errors='coerce' wandelt Fehler bei der Konvertierung in NaN. Wichtig, da Imputer NaNs erwartet.
|
||||
numeric_features_to_convert = [
|
||||
'Log_Finaler_Umsatz_ML', 'Log_Finaler_Mitarbeiter_ML', 'Umsatz_pro_MA_ML', 'Anzahl_Servicetechniker_Numeric'
|
||||
]
|
||||
for col in numeric_features_to_convert:
|
||||
if col in df_model_ready.columns:
|
||||
df_model_ready[col] = pd.to_numeric(df_model_ready[col], errors='coerce')
|
||||
|
||||
|
||||
# Setzen Sie den Index des DataFrames zurueck, um eine saubere Verarbeitung in den naechsten Schritten
|
||||
# (z.B. Train/Test-Split) sicherzustellen. drop=True verhindert, dass der alte Index als neue Spalte hinzugefuegt wird.
|
||||
df_model_ready = df_model_ready.reset_index(drop=True)
|
||||
|
||||
self.logger.info("Datenvorbereitung fuer Modellierung (Training) abgeschlossen.")
|
||||
self.logger.info(f"Finaler DataFrame fuer Modellierung hat {len(df_model_ready)} Zeilen und {len(df_model_ready.columns)} Spalten.")
|
||||
self.logger.info(f"Anzahl Feature-Spalten: {len(feature_columns_ml)}")
|
||||
|
||||
# Logge Informationen zum finalen DataFrame
|
||||
self.logger.info("Datenvorbereitung fuer Modellierung (Training) abgeschlossen.") # <<< GEÄNDERT
|
||||
self.logger.info(f"Finaler DataFrame fuer Modellierung hat {len(df_model_ready)} Zeilen und {len(df_model_ready.columns)} Spalten.") # <<< GEÄNDERT
|
||||
# Logge die Anzahl der Feature-Spalten, nicht die Liste selbst (kann sehr lang sein).
|
||||
self.logger.info(f"Anzahl Feature-Spalten: {len(feature_columns)}") # <<< GEÄNDERT
|
||||
self.logger.info(f"Ziel-Spalte: {target_column}") # <<< GEÄNDERT
|
||||
|
||||
|
||||
# WICHTIG: Info ueber fehlende Werte in den finalen numerischen Features VOR der Imputation.
|
||||
# Die Imputation selbst erfolgt im Trainingsschritt (train_technician_model Block 31).
|
||||
numeric_features_for_imputation = ['Finaler_Umsatz', 'Finaler_Mitarbeiter']
|
||||
nan_counts = df_model_ready[numeric_features_for_imputation].isna().sum()
|
||||
self.logger.info(f"Fehlende Werte in numerischen Features vor Imputation:\n{nan_counts}") # <<< GEÄNDERT
|
||||
# Logge auch, wie viele Zeilen *mindestens* einen NaN in den numerischen Features haben.
|
||||
rows_with_nan = df_model_ready[numeric_features_for_imputation].isna().any(axis=1).sum()
|
||||
self.logger.info(f"Anzahl Zeilen mit mindestens einem fehlenden numerischen Feature (vor Imputation): {rows_with_nan}") # <<< GEÄNDERT
|
||||
|
||||
|
||||
return df_model_ready # Gebe den vorbereiteten DataFrame zurueck
|
||||
numeric_features_for_imputation_ml = [
|
||||
'Log_Finaler_Umsatz_ML',
|
||||
'Log_Finaler_Mitarbeiter_ML',
|
||||
'Umsatz_pro_MA_ML'
|
||||
]
|
||||
existing_numeric_features = [col for col in numeric_features_for_imputation_ml if col in df_model_ready.columns]
|
||||
if existing_numeric_features:
|
||||
nan_counts = df_model_ready[existing_numeric_features].isna().sum()
|
||||
self.logger.info(f"Fehlende Werte in numerischen Features vor Imputation:\n{nan_counts}")
|
||||
rows_with_nan = df_model_ready[existing_numeric_features].isna().any(axis=1).sum()
|
||||
self.logger.info(f"Anzahl Zeilen mit mindestens einem fehlenden numerischen Feature (vor Imputation): {rows_with_nan}")
|
||||
|
||||
return df_model_ready
|
||||
|
||||
|
||||
# Methode zum Trainieren des ML Modells
|
||||
|
||||
Reference in New Issue
Block a user