Skip to content

Text Cleaning

src.preprocessing.text_cleaner.TextCleaner

Bases: BaseEstimator, TransformerMixin

Ein benutzerdefinierter Transformer zur Bereinigung von Textdaten mit spaCy.

Dieser Transformer führt eine Reihe von Operationen durch, um Text zu normalisieren und für weitere NLP-Aufgaben vorzubereiten. Er ist kompatibel mit scikit-learn Pipelines und kann Strings, Listen, NumPy-Arrays und Pandas DataFrames verarbeiten. Bei DataFrames werden automatisch nur die Textspalten bereinigt.

Parameters:

Name Type Description Default
language str

Die Sprache des Textes ('english' oder 'german'). Bestimmt das zu ladende spaCy-Modell. Standardwert ist 'english'.

'english'
spacy_model_map dict

Ein Mapping von 'language' zu spaCy-Modellnamen. Kann angepasst werden, um z.B. größere Modelle zu verwenden ('de_core_news_md').

{'english': 'en_core_web_sm', 'german': 'de_core_news_sm'}
to_lowercase bool

Ob der Text in Kleinbuchstaben umgewandelt werden soll. Standardwert ist True.

True
remove_punctuation bool

Ob Satzzeichen entfernt werden sollen. Dies geschieht mittels token.is_punct von spaCy. Standardwert ist True.

True
remove_stopwords bool

Ob Stopwörter entfernt werden sollen. Verwendet die Stopwörterliste des spaCy-Modells. Standardwert ist True.

True
apply_lemmatization bool

Ob Lemmatisierung angewendet werden soll. Verwendet token.lemma_ von spaCy. Wenn False, wird token.text verwendet. Standardwert ist True.

True
replace_digits_with str or None

Ein String, mit dem Ziffern ersetzt werden sollen. Wenn None, bleiben Ziffern erhalten. Beispiel: "ZAHL" oder "". Standardwert ist None.

None
replace_html_with str or None

Ein String, mit dem HTML-Tags ersetzt

''
replace_mailaddresses_with str or None

Ein String, mit dem E-Mail-Adressen ersetzt werden sollen. Wenn None, bleiben sie erhalten. Standardwert ist ''.

''
whitespace_normalization bool

Ob überflüssige Leerzeichen am Ende des Prozesses normalisiert werden sollen. Standardwert ist True.

True
Source code in src\preprocessing\text_cleaner.py
class TextCleaner(BaseEstimator, TransformerMixin):
    """
    Ein benutzerdefinierter Transformer zur Bereinigung von Textdaten mit spaCy.

    Dieser Transformer führt eine Reihe von Operationen durch, um Text zu normalisieren
    und für weitere NLP-Aufgaben vorzubereiten. Er ist kompatibel mit scikit-learn
    Pipelines und kann Strings, Listen, NumPy-Arrays und Pandas DataFrames verarbeiten.
    Bei DataFrames werden automatisch nur die Textspalten bereinigt.

    Args:
        language (str, optional): Die Sprache des Textes ('english' oder 'german').
            Bestimmt das zu ladende spaCy-Modell. Standardwert ist 'english'.

        spacy_model_map (dict, optional): Ein Mapping von 'language' zu
            spaCy-Modellnamen. Kann angepasst werden, um z.B. größere Modelle zu
            verwenden ('de_core_news_md').

        to_lowercase (bool, optional): Ob der Text in Kleinbuchstaben umgewandelt werden
            soll. Standardwert ist True.

        remove_punctuation (bool, optional): Ob Satzzeichen entfernt werden sollen.
            Dies geschieht mittels `token.is_punct` von spaCy. Standardwert ist True.

        remove_stopwords (bool, optional): Ob Stopwörter entfernt werden sollen.
            Verwendet die Stopwörterliste des spaCy-Modells. Standardwert ist True.

        apply_lemmatization (bool, optional): Ob Lemmatisierung angewendet werden soll.
            Verwendet `token.lemma_` von spaCy. Wenn False, wird `token.text` verwendet.
            Standardwert ist True.

        replace_digits_with (str or None, optional): Ein String, mit dem Ziffern ersetzt
            werden sollen. Wenn None, bleiben Ziffern erhalten. Beispiel: "ZAHL" oder
            "". Standardwert ist None.

        replace_html_with (str or None, optional): Ein String, mit dem HTML-Tags ersetzt
        werden
            sollen. Wenn None, werden sie nicht behandelt. Standardwert ist ''.

        replace_mailaddresses_with (str or None, optional): Ein String, mit dem
            E-Mail-Adressen ersetzt werden sollen. Wenn None, bleiben sie erhalten.
            Standardwert ist ''.

        whitespace_normalization (bool, optional): Ob überflüssige Leerzeichen
            am Ende des Prozesses normalisiert werden sollen. Standardwert ist True.
    """

    def __init__(
        self,
        language: Literal["english", "german"] = "english",
        spacy_model_map={
            "english": "en_core_web_sm",
            "german": "de_core_news_sm",
        },
        to_lowercase=True,
        remove_punctuation=True,
        remove_stopwords=True,
        apply_lemmatization=True,
        replace_digits_with=None,
        replace_html_with="",
        replace_mailaddresses_with="",
        whitespace_normalization=True,
    ):
        self.language = language
        self.to_lowercase = to_lowercase
        self.remove_punctuation = remove_punctuation
        self.remove_stopwords = remove_stopwords
        self.apply_lemmatization = apply_lemmatization
        self.replace_digits_with = replace_digits_with
        self.replace_html_with = replace_html_with
        self.replace_mailaddresses_with = replace_mailaddresses_with
        self.whitespace_normalization = whitespace_normalization

        self.spacy_model_map = spacy_model_map

        if self.language not in self.spacy_model_map:
            raise ValueError(
                f"Language '{self.language}' is not supported. "
                f"Supported languages are: {list(self.spacy_model_map.keys())}"
            )

        self.nlp = None

    def _initialize_spacy_model(self):
        """Lädt das benötigte spaCy-Modell."""
        model_name = self.spacy_model_map[self.language]
        try:
            self.nlp = spacy.load(model_name)
        except OSError:
            print(f"spaCy model '{model_name}' not found. Please run:")
            print(f"python -m spacy download {model_name}")
            raise

    def fit(self, X, y=None):
        """
        Bereitet den Transformer vor, indem das spaCy-Modell geladen wird.

        Args:
            X: Die Eingabedaten. Werden nicht zum Trainieren verwendet, nur zur
                Kompatibilität.

            y: Ignored.

        Returns:
            self (object): Gibt die Instanz des Transformers zurück.
        """
        self._initialize_spacy_model()
        return self

    def transform(self, X, y=None):
        """
        Wendet die Textbereinigungsoperationen auf die Eingabedaten an.

        Akzeptiert Strings, Listen, NumPy-Arrays und Pandas DataFrames.

        Args:
            X: Die zu bereinigenden Daten.
            y: Ignored.

        Returns:
            Die bereinigten Daten im passenden Format (str, np.array, pd.DataFrame).
        """
        if self.nlp is None:
            raise RuntimeError("The fit method must be called before transform.")

        # Dispatcher für verschiedene Eingabetypen
        if isinstance(X, str):
            return self._process_text(X)

        if isinstance(X, (list, tuple)):
            # Verarbeite jedes Element der Liste und gib ein NumPy Array zurück
            return np.array([self._process_text(str(text)) for text in X], dtype=object)

        if isinstance(X, np.ndarray):
            if X.ndim != 1:
                raise ValueError("Input NumPy array must be 1-dimensional.")
            # np.vectorize ist eine elegante Alternative zur Schleife für NumPy Arrays
            v_process_text = np.vectorize(self._process_text)
            return v_process_text(X.astype(str))

        if isinstance(X, pd.DataFrame):
            X_processed = X.copy()
            for col in X_processed.columns:
                # Verarbeite nur Spalten, die wahrscheinlich Text enthalten
                if pd.api.types.is_string_dtype(
                    X_processed[col]
                ) or pd.api.types.is_object_dtype(X_processed[col]):
                    # Stelle sicher, dass alle Werte in der Spalte Strings sind, fülle
                    # NaNs
                    X_processed[col] = (
                        X_processed[col]
                        .fillna("")
                        .astype(str)
                        .apply(self._process_text)
                    )
            return X_processed

        if isinstance(X, pd.Series):
            X_processed = X.copy()
            if pd.api.types.is_string_dtype(
                X_processed
            ) or pd.api.types.is_object_dtype(X_processed):
                X_processed = (
                    X_processed.fillna("").astype(str).apply(self._process_text)
                )
            return X_processed

        raise TypeError(
            f"Input type {type(X).__name__} is not supported. "
            "Please provide a str, list, numpy array, or pandas DataFrame."
        )

    def _process_text(self, text: str) -> str:
        """Führt alle aktivierten Bereinigungsschritte für einen einzelnen Text-String
        durch."""
        if not isinstance(text, str) or not text.strip():
            return ""

        # --- Schritte vor der spaCy-Verarbeitung (mit Regex) ---
        if self.replace_html_with is not None:
            text = re.sub(r"<[^>]*>", self.replace_html_with, text)

        if self.replace_mailaddresses_with is not None:
            text = re.sub(
                r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b",
                self.replace_mailaddresses_with,
                text,
            )

        if self.replace_digits_with is not None:
            text = re.sub(r"\d+", self.replace_digits_with, text)

        # --- spaCy-Verarbeitung ---
        # Deaktivieren unnötiger Pipes für bessere Performance
        disabled_pipes = []
        if not self.apply_lemmatization:
            # Parser wird für die Lemmatisierung benötigt
            disabled_pipes.extend(["parser", "ner"])

        doc = self.nlp(text, disable=disabled_pipes)

        tokens = []
        for token in doc:
            # Filtern basierend auf spaCy-Attributen
            if self.remove_stopwords and token.is_stop:
                continue
            if self.remove_punctuation and token.is_punct:
                continue
            if token.is_space:  # Leere Token/Whitespace überspringen
                continue

            # Lemmatisierung oder Originaltext des Tokens
            if self.apply_lemmatization:
                tokens.append(token.lemma_)
            else:
                tokens.append(token.text)

        # --- Schritte nach der Tokenisierung ---
        processed_text = " ".join(tokens)

        if self.to_lowercase:
            processed_text = processed_text.lower()

        if self.whitespace_normalization:
            processed_text = re.sub(r"\s+", " ", processed_text).strip()

        return processed_text

    def __getstate__(self):
        state = self.__dict__.copy()
        state["nlp"] = None  # Don't pickle the loaded spaCy pipeline
        return state

    def __setstate__(self, state):
        self.__dict__ = state
        self._initialize_spacy_model()  # Re-load the spaCy model on unpickling

fit(X, y=None)

Bereitet den Transformer vor, indem das spaCy-Modell geladen wird.

Parameters:

Name Type Description Default
X

Die Eingabedaten. Werden nicht zum Trainieren verwendet, nur zur Kompatibilität.

required
y

Ignored.

None

Returns:

Name Type Description
self object

Gibt die Instanz des Transformers zurück.

Source code in src\preprocessing\text_cleaner.py
def fit(self, X, y=None):
    """
    Bereitet den Transformer vor, indem das spaCy-Modell geladen wird.

    Args:
        X: Die Eingabedaten. Werden nicht zum Trainieren verwendet, nur zur
            Kompatibilität.

        y: Ignored.

    Returns:
        self (object): Gibt die Instanz des Transformers zurück.
    """
    self._initialize_spacy_model()
    return self

transform(X, y=None)

Wendet die Textbereinigungsoperationen auf die Eingabedaten an.

Akzeptiert Strings, Listen, NumPy-Arrays und Pandas DataFrames.

Parameters:

Name Type Description Default
X

Die zu bereinigenden Daten.

required
y

Ignored.

None

Returns:

Type Description

Die bereinigten Daten im passenden Format (str, np.array, pd.DataFrame).

Source code in src\preprocessing\text_cleaner.py
def transform(self, X, y=None):
    """
    Wendet die Textbereinigungsoperationen auf die Eingabedaten an.

    Akzeptiert Strings, Listen, NumPy-Arrays und Pandas DataFrames.

    Args:
        X: Die zu bereinigenden Daten.
        y: Ignored.

    Returns:
        Die bereinigten Daten im passenden Format (str, np.array, pd.DataFrame).
    """
    if self.nlp is None:
        raise RuntimeError("The fit method must be called before transform.")

    # Dispatcher für verschiedene Eingabetypen
    if isinstance(X, str):
        return self._process_text(X)

    if isinstance(X, (list, tuple)):
        # Verarbeite jedes Element der Liste und gib ein NumPy Array zurück
        return np.array([self._process_text(str(text)) for text in X], dtype=object)

    if isinstance(X, np.ndarray):
        if X.ndim != 1:
            raise ValueError("Input NumPy array must be 1-dimensional.")
        # np.vectorize ist eine elegante Alternative zur Schleife für NumPy Arrays
        v_process_text = np.vectorize(self._process_text)
        return v_process_text(X.astype(str))

    if isinstance(X, pd.DataFrame):
        X_processed = X.copy()
        for col in X_processed.columns:
            # Verarbeite nur Spalten, die wahrscheinlich Text enthalten
            if pd.api.types.is_string_dtype(
                X_processed[col]
            ) or pd.api.types.is_object_dtype(X_processed[col]):
                # Stelle sicher, dass alle Werte in der Spalte Strings sind, fülle
                # NaNs
                X_processed[col] = (
                    X_processed[col]
                    .fillna("")
                    .astype(str)
                    .apply(self._process_text)
                )
        return X_processed

    if isinstance(X, pd.Series):
        X_processed = X.copy()
        if pd.api.types.is_string_dtype(
            X_processed
        ) or pd.api.types.is_object_dtype(X_processed):
            X_processed = (
                X_processed.fillna("").astype(str).apply(self._process_text)
            )
        return X_processed

    raise TypeError(
        f"Input type {type(X).__name__} is not supported. "
        "Please provide a str, list, numpy array, or pandas DataFrame."
    )

— André Gasch