Существует ли идиома Pandas для чтения файла CSV с категориальными данными, имеющими варианты написания?

У меня есть файл CSV с несколькими категориальными столбцами, но большинство этих столбцов содержат беспорядочные данные из-за ошибок ввода (например, «спицулированный», «спицулированный» и т. д. для категории «спицулированный» в столбце «поля»). Есть ли стандартный способ справиться с такими ситуациями?

Точнее, я хотел бы прочитать CSV-файл непосредственно в чистый DataFrame с категорией dtype для категориальных столбцов, но со всеми вариантами, свернутыми в одну категорию (например, каждый вариант «spiculated» будет читаться как «spiculated» ). Варианты написания могут быть заданы, например, диктатом.

Ожидаемое решение:

import pandas as pd

FEAT_VALS = {
    "margins": {
        "spiculated": ["spiculated", "spiiculated", "SPICULATED"],
        "circumscribed": ["circumscribed", "cicumscribed"],
    }
}

# somehow give FEAT_VALS to read_csv
df = pd.read_csv('test.csv', dtype='category')
df.margins

где test.csv:

margins
spiculated
spiiculated
SPICULATED
circumscribed
cicumscribed

чтобы получить:

0       spiculated
1       spiculated
2       spiculated
3    circumscribed
4    circumscribed
Name: margins, dtype: category
Categories (2, object): ['circumscribed', 'spiculated']

Однако без информации о вариантах написания я получаю:

0       spiculated
1      spiiculated
2       SPICULATED
3    circumscribed
4     cicumscribed
Name: margins, dtype: category
Categories (5, object): ['SPICULATED', 'cicumscribed', 'circumscribed', 'spiculated', 'spiiculated']

Мое текущее решение: выглядит так

df2 = pd.read_csv('test.csv')

for feat, feat_vals in FEAT_VALS.items():
    for enc_val, str_vals in feat_vals.items():
        df2.loc[df2[feat].isin(str_vals), feat] = enc_val

df2.margins = df2.margins.astype('category')

пожалуйста, предоставьте минимальный воспроизводимый пример с вашими входными данными и ожидаемым результатом: stackoverflow.com/help/minimal-reproducible-example

iBeMeltin 15.07.2024 19:53

Знаете ли вы правильные категории заранее? Если нет, то вам придется использовать алгоритм кластеризации вместо расстояния Левенштейна.

Fravadona 15.07.2024 20:13

@Фравадона Да.

Alek Fröhlich 15.07.2024 20:31

@AlekFröhlich Мы не сможем дать никакого ответа, если вы не предоставите несколько примеров строк CSV-файла (лучше упрощенный), а также ожидаемый результат, который вы хотите получить из него.

Fravadona 16.07.2024 13:56

@Fravadona Обновлено соответственно.

Alek Fröhlich 16.07.2024 14:32

@iBeMeltin Я считаю, что моего редактирования должно быть достаточно. Пожалуйста, скажите мне, если что-то еще не хватает.

Alek Fröhlich 16.07.2024 14:34
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
6
109
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

В вашем вопросе следует учитывать три вещи:

  1. Сделайте строки несколько сопоставимыми (например, удалите акценты и используйте один и тот же регистр).
    Как лучше всего удалить акценты (нормализовать) в строке Юникода Python?

  2. Выберите алгоритм/библиотеку подходящего расстояния.
    ищу библиотеку Python, которая может выполнять расстояние редактирования Левенштейна/другого на уровне слова

  3. Создайте DataFrame из преобразованных входных данных.
    Создать DataFrame pandas из генератора?


Вот пример реализации, в которой для сравнения используется расстояние Левенштейна; теперь вам просто нужно указать ожидаемые категории (т. е. больше нет FEAT_VALS). Кроме того, ленивое обновление CSV означает, что объем памяти сохраняется как можно меньше.

import csv
import unicodedata
import Levenshtein
import pandas as pd

def normalize_string(str):
    return ''.join(
        char for char in unicodedata.normalize('NFD', str)
        if unicodedata.category(char) != 'Mn'
    ).lower()

def parse_csv(filename, categories = ["spiculated", "circumscribed"]):
    with open(filename) as f:
        reader = csv.DictReader(f)
        normalized_categories = [normalize_string(str) for str in categories]
        for row in reader:
            min_distance = float('inf')
            normalized_margins = normalize_string(row["margins"])
            for i,normalized_category in enumerate(normalized_categories):
                distance = Levenshtein.distance(
                    normalized_margins,
                    normalized_category
                )
                if distance < min_distance:
                    min_distance = distance
                    row["margins"] = categories[i]
            yield row

df = pd.DataFrame(data=parse_csv("test.csv"), dtype='category')

print(df.margins)

выход:

0       spiculated
1       spiculated
2       spiculated
3    circumscribed
4    circumscribed
Name: margins, dtype: category
Categories (2, object): ['circumscribed', 'spiculated']

Очень хорошая идея, спасибо!

Alek Fröhlich 17.07.2024 18:35
Ответ принят как подходящий

Вы можете перевернуть внутренние словари, а затем использовать .map(). Это не так просто, как вам хотелось бы, но, по крайней мере, код, использующий Pandas, стал чище.

for feat, feat_vals in FEAT_VALS.items():
    feat_strs = {
        str_val: enc_val
        for enc_val, str_vals in feat_vals.items()
        for str_val in str_vals
    }
    df[feat] = df[feat].map(feat_strs).astype('category')

    # For demo
    print(df[feat].cat.categories)

Выход:

Index(['circumscribed', 'spiculated'], dtype='object')

Я вижу, спасибо!

Alek Fröhlich 17.07.2024 18:38

Другие вопросы по теме