Към съдържанието
wannacode

Чистене на данни

Накратко: Чистенето на данни маха дубликатите, привежда цени, дати и имена в един формат и проверява с код, че след почистването данните са верни.

Проблем

Новата ценова листа

Нов доставчик прати ценовата си листа като CSV. Управителят пита две неща: колко средно ще плащаме за продукт и колко ни струва кафето. Файлът supplier.csv е до кода. Ето го:

Pythonданни: таблиците на магазина

Всичките четири колони са str. Цените са и „0,95 €“, и „1.45“, и празно, и „по запитване“. Датите са в два формата. Сиренето е два пъти. Имената са с главни, малки и интервали. Кафето струва 1190.

prices['price'].mean() дори не тръгва: цените са текст. Такива данни се казват мръсни и са нормалното, а не изключението. Програмата на доставчика пише както пише, а хората попълват на ръка.

Идеята

Почистването е код, не ръчна работа

Изкушението е да отвориш файла и да поправиш клетките на ръка. Но след месец доставчикът ще прати нова листа със същите грешки. Затова почистването е код: върви наново върху всеки нов файл.

Пътят е един и същ:

  1. Виж какво не е наред: dtypes, describe, value_counts, подреждане.
  2. Поправи с една стъпка за всеки вид мръсотия: дубликати, текст, числа, дати, изключения.
  3. Провери с код, че е станало: броят редове преди и след, NaN само там, където трябва.

Всяка стъпка от този урок е за един от видовете мръсотия в листата.

Идеята

Дубликати: duplicated и drop_duplicates

duplicated() дава маска: True за всеки ред, който повтаря някой ред над себе си. .sum() ги брои. С subset= сравнява само дадените колони, а keep=False маркира всички копия, не само второто:

Pythonданни: таблиците на магазина

Съвсем еднакъв ред има един: сиренето, P07. По код обаче повторенията са две. Прясното мляко, P05, е два пъти, веднъж като „ПРЯСНО МЛЯКО“ и веднъж като „ Прясно мляко“, с интервал отпред. За pandas това са различни текстове, затова duplicated() не го хваща.

drop_duplicates() маха повторенията и оставя първия ред. Но ако го пуснеш сега, млякото ще остане два пъти.

Идеята

Имена: str.strip и str.lower

Текстът се чисти с .str от урока за типовете. str.strip() маха интервалите в краищата, а str.lower() прави всички букви малки. Така „ПРЯСНО МЛЯКО“ и „ Прясно мляко“ стават един и същ текст:

Pythonданни: таблиците на магазина

От 16 реда остават 14: сиренето и млякото са махнати. Редът на стъпките е важен: първо се чисти текстът, после се търсят дубликати.

По-точно, за любопитните

str.lower() е за сравняване. За етикет или отчет str.capitalize() прави първата буква главна: „прясно мляко“ става „Прясно мляко“. Ако имената се различават и в средата (два интервала между думите), str.replace(' ', ' ') или str.split().str.join(' ') ги изравнява.

Пробвай

Без повторения

Изчисти имената в листата (без интервали в краищата, с малки букви), махни повторените редове и запиши в rows колко реда остават.

Pythonданни: таблиците на магазина

Идеята

Цени от текст: str.replace и pd.to_numeric

„0,95 €“ е текст по три причини: знакът €, запетаята вместо точка и интервалът. str.replace маха всяка от тях. После pd.to_numeric прави текста на число. С errors='coerce' всичко, което пак не е число, става NaN, вместо да спре кода:

Pythonданни: таблиците на магазина

Цената е float64, а NaN са две: ябълките (празно поле) и минералната вода („по запитване“). Това е вярно: доставчикът не е дал цена за тях. Без errors='coerce' to_numeric спира с ValueError: Unable to parse string "по запитване".

errors='coerce' е удобен и опасен: не казва какво е изхвърлил. Затова броиш NaN преди и след. Ако след превръщането са повече от празните полета, нещо е станало NaN по грешка.

Пробвай

Цените като числа

Направи колоната price число. NaN трябва да остане само там, където доставчикът не е дал цена.

Pythonданни: таблиците на магазина

Виж го

Дати в два формата

Половината дати са „01.11.2026“ (ден, месец, година), а другата половина „2026-11-01“. Ако оставиш pandas да гадае:

Pythonданни: таблиците на магазина

pandas гадае формата от първата дата и я чете като %m.%d.%Y: месец отпред, по американски. На ISO датата спира с грешка. Ако всички дати бяха с точки, нямаше да има грешка: 01.11 щеше тихо да стане 11 януари.

Затова казваш формата сам, с format=, по веднъж за всеки формат. С errors='coerce' датите в другия формат стават NaT, а fillna слива двата прочита:

Pythonданни: таблиците на магазина

13 реда важат от 1 ноември и 2 от 15 ноември. Една дата не се чете с нито един формат: 31.11.2026 при бананите. Такъв ден няма, ноември има 30 дни.

1. След двата прочита на датите 31.11.2026 остава NaT. Какво правиш с реда на бананите?

Виж го

Изключения: describe и подреждане

Цените вече са числа. describe() от първия поглед казва дали изглеждат правдоподобно, а подреждането показва кои са най-големите:

Pythonданни: таблиците на магазина

Средната е 102,70 €, а медианата 1,62 €. Такава разлика значи, че една стойност е далеч от останалите. Това се казва изключение (outlier). Подреждането го намира веднага: кафето на зърна за 1190 €. Ние продаваме кафето за 14,90 €, значи доставната цена е 11,90 €: програмата на доставчика е изпуснала десетичната точка.

Поправката е правило за всички цени над 100 €. Пишеш го в кода, а не в една клетка, за да хване същата грешка и в следващата листа:

Pythonданни: таблиците на магазина

Кафето струва 11,90 €, средната падна на 4,53 €, а медианата остана 1,62 €. Медианата не усеща едно огромно число, а средната го усеща цялото.

По-точно, за любопитните

Правилото „над 100 € значи изпусната точка“ е вярно за тази листа, защото знаем продуктите: нищо в нея не струва над 100 €. В друга листа (телевизори, мебели) същото правило би развалило верни цени. Когато не знаеш защо стойността е странна, питай откъдето е дошла, вместо да я поправяш.

Идеята

Проверките като код: assert

Почистването е готово, когато можеш да го докажеш. assert условие, 'съобщение' не прави нищо, ако условието е вярно, и спира кода с AssertionError и съобщението, ако не е. Ето цялото почистване с проверките в края:

Pythonданни: таблиците на магазина

От 16 реда остават 14. Проверките казват на глас какво очакваш:

  • всеки код е веднъж;
  • цена липсва само на двата продукта без цена;
  • всяка цена е между 0,10 € и 100 €;
  • неразчетената дата е само една.

Когато следващата листа дойде с нова грешка, проверката ще спре кода с ясно съобщение, вместо отчетът тихо да излезе грешен.

Упражнение

Средната доставна цена

Отговори на управителя. Изчисти цялата листа в prices: имената и дубликатите, цените като числа, датите в двата формата и кафето без десетичната точка. Продуктите без цена остават, с NaN. Запиши в avg_price средната цена, закръглена до 2 знака.

Pythonданни: таблиците на магазина

Тест

Провери се

1. В листата прясното мляко е два пъти: ПРЯСНО МЛЯКО и Прясно мляко. Колко дубликата намира drop_duplicates(), преди да изчистиш имената?
2. Защо pd.to_numeric(prices['price'], errors='coerce') без нищо друго е опасно?
3. Средната цена е 102,70 €, а медианата е 1,62 €. Какво казва това?
4. Кое е по-добро след почистването?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·