У меня есть файл 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')
Знаете ли вы правильные категории заранее? Если нет, то вам придется использовать алгоритм кластеризации вместо расстояния Левенштейна.
@Фравадона Да.
@AlekFröhlich Мы не сможем дать никакого ответа, если вы не предоставите несколько примеров строк CSV-файла (лучше упрощенный), а также ожидаемый результат, который вы хотите получить из него.
@Fravadona Обновлено соответственно.
@iBeMeltin Я считаю, что моего редактирования должно быть достаточно. Пожалуйста, скажите мне, если что-то еще не хватает.






В вашем вопросе следует учитывать три вещи:
Сделайте строки несколько сопоставимыми (например, удалите акценты и используйте один и тот же регистр).
Как лучше всего удалить акценты (нормализовать) в строке Юникода Python?
Выберите алгоритм/библиотеку подходящего расстояния.
ищу библиотеку Python, которая может выполнять расстояние редактирования Левенштейна/другого на уровне слова
Создайте 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']
Очень хорошая идея, спасибо!
Вы можете перевернуть внутренние словари, а затем использовать .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')
Я вижу, спасибо!
пожалуйста, предоставьте минимальный воспроизводимый пример с вашими входными данными и ожидаемым результатом: stackoverflow.com/help/minimal-reproducible-example