Очистка данных с помощью pandas и NumPy

Оглавление

Специалисты по изучению данных тратят много времени на очистку наборов данных и приведение их в форму, с которой можно работать. Фактически, многие специалисты по изучению данных утверждают, что начальные шаги по получению и очистке данных составляют 80 % работы.

Поэтому, если вы только начинаете работать в этой области или планируете начать работать в ней, важно уметь работать с беспорядочными данными, будь то пропущенные значения, непоследовательное форматирование, неправильные записи или нелепые выбросы.

В этом уроке мы будем использовать библиотеки Python pandas и NumPy для очистки данных.

Мы расскажем о следующем:

  • Удаление ненужных столбцов в DataFrame
  • Изменение индекса DataFrame
  • Использование методов .str() для очистки столбцов
  • Использование функции DataFrame.applymap() для очистки всего набора данных по элементам
  • Переименование столбцов в более узнаваемый набор меток
  • Удаление ненужных строк в CSV-файле

Бесплатный бонус: Нажмите здесь, чтобы получить доступ к бесплатному руководству по ресурсам NumPy, которое поможет вам найти лучшие учебники, видео и книги для улучшения навыков работы с NumPy.

Вот наборы данных, которые мы будем использовать:

  • BL-Flickr-Images-Book.csv - CSV-файл, содержащий информацию о книгах из Британской библиотеки
  • university_towns.txt - Текстовый файл, содержащий названия городов колледжей в каждом штате США
  • olympics.csv - CSV-файл, содержащий информацию об участии всех стран в летних и зимних Олимпийских играх

Вы можете загрузить наборы данных из репозитория Real Python GitHub для того, чтобы следовать приведенным здесь примерам.

Примечание: Я рекомендую использовать Jupyter Notebooks, чтобы следить за ходом работы.

Этот учебник предполагает базовое понимание библиотек pandas и NumPy, включая рабочие лошадки Панды Series и DataFrame объекты , общие методы, которые могут быть применены к этим объектам, и знакомство с NaN значениями NumPy.

Давайте импортируем необходимые модули и приступим к работе!

>>> import pandas as pd
>>> import numpy as np

Уменьшение столбцов в DataFrame

Часто вы обнаружите, что не все категории данных в наборе данных полезны для вас. Например, у вас может быть набор данных, содержащий информацию об ученике (имя, класс, стандарт, имена родителей и адрес), но вы хотите сосредоточиться на анализе оценок ученика.

В этом случае категории адреса или имен родителей для вас не важны. Сохранение этих ненужных категорий будет занимать лишнее место и, возможно, загромождать время выполнения.

pandas предоставляет удобный способ удаления ненужных столбцов или строк из DataFrame с помощью функции drop(). Рассмотрим простой пример, в котором мы удаляем несколько столбцов из DataFrame.

Сначала создадим DataFrame из CSV-файла 'BL-Flickr-Images-Book.csv'. В примерах ниже мы передаем относительный путь к pd.read_csv, что означает, что все наборы данных находятся в папке с именем Datasets в нашем текущем рабочем каталоге:

>>> df = pd.read_csv('Datasets/BL-Flickr-Images-Book.csv')
>>> df.head()

    Identifier             Edition Statement      Place of Publication  \
0         206                           NaN                    London
1         216                           NaN  London; Virtue & Yorston
2         218                           NaN                    London
3         472                           NaN                    London
4         480  A new edition, revised, etc.                    London

  Date of Publication              Publisher  \
0         1879 [1878]       S. Tinsley & Co.
1                1868           Virtue & Co.
2                1869  Bradbury, Evans & Co.
3                1851          James Darling
4                1857   Wertheim & Macintosh

                                               Title     Author  \
0                  Walter Forbes. [A novel.] By A. A      A. A.
1  All for Greed. [A novel. The dedication signed...  A., A. A.
2  Love the Avenger. By the author of “All for Gr...  A., A. A.
3  Welsh Sketches, chiefly ecclesiastical, to the...  A., E. S.
4  [The World in which I live, and my place in it...  A., E. S.

                                   Contributors  Corporate Author  \
0                               FORBES, Walter.               NaN
1  BLAZE DE BURY, Marie Pauline Rose - Baroness               NaN
2  BLAZE DE BURY, Marie Pauline Rose - Baroness               NaN
3                   Appleyard, Ernest Silvanus.               NaN
4                           BROOME, John Henry.               NaN

   Corporate Contributors Former owner  Engraver Issuance type  \
0                     NaN          NaN       NaN   monographic
1                     NaN          NaN       NaN   monographic
2                     NaN          NaN       NaN   monographic
3                     NaN          NaN       NaN   monographic
4                     NaN          NaN       NaN   monographic

                                          Flickr URL  \
0  http://www.flickr.com/photos/britishlibrary/ta...
1  http://www.flickr.com/photos/britishlibrary/ta...
2  http://www.flickr.com/photos/britishlibrary/ta...
3  http://www.flickr.com/photos/britishlibrary/ta...
4  http://www.flickr.com/photos/britishlibrary/ta...

                            Shelfmarks
0    British Library HMNTS 12641.b.30.
1    British Library HMNTS 12626.cc.2.
2    British Library HMNTS 12625.dd.1.
3    British Library HMNTS 10369.bbb.15.
4    British Library HMNTS 9007.d.28.

Когда мы рассматриваем первые пять записей, используя метод head(), мы видим, что несколько столбцов содержат вспомогательную информацию, которая была бы полезна для библиотеки, но не очень описывает сами книги: Edition Statement, Corporate Author, Corporate Contributors, Former owner, Engraver, Issuance type и Shelfmarks.

Мы можем отказаться от этих столбцов следующим образом:

>>> to_drop = ['Edition Statement',
...            'Corporate Author',
...            'Corporate Contributors',
...            'Former owner',
...            'Engraver',
...            'Contributors',
...            'Issuance type',
...            'Shelfmarks']

>>> df.drop(to_drop, inplace=True, axis=1)

Выше мы определили список, содержащий имена всех столбцов, которые мы хотим удалить. Далее мы вызываем функцию drop() на нашем объекте, передавая параметр inplace как True, а параметр axis как 1. Это говорит pandas, что мы хотим, чтобы изменения были сделаны непосредственно в нашем объекте и что он должен искать значения, которые нужно опустить, в столбцах объекта.

Когда мы снова просмотрим DataFrame, мы увидим, что ненужные столбцы были удалены:

>>> df.head()
   Identifier      Place of Publication Date of Publication  \
0         206                    London         1879 [1878]
1         216  London; Virtue & Yorston                1868
2         218                    London                1869
3         472                    London                1851
4         480                    London                1857

               Publisher                                              Title  \
0       S. Tinsley & Co.                  Walter Forbes. [A novel.] By A. A
1           Virtue & Co.  All for Greed. [A novel. The dedication signed...
2  Bradbury, Evans & Co.  Love the Avenger. By the author of “All for Gr...
3          James Darling  Welsh Sketches, chiefly ecclesiastical, to the...
4   Wertheim & Macintosh  [The World in which I live, and my place in it...

      Author                                         Flickr URL
0      A. A.  http://www.flickr.com/photos/britishlibrary/ta...
1  A., A. A.  http://www.flickr.com/photos/britishlibrary/ta...
2  A., A. A.  http://www.flickr.com/photos/britishlibrary/ta...
3  A., E. S.  http://www.flickr.com/photos/britishlibrary/ta...
4  A., E. S.  http://www.flickr.com/photos/britishlibrary/ta...

Альтернативный вариант - удалить столбцы, передав их в параметр columns напрямую, вместо того чтобы отдельно указывать метки, которые нужно удалить, и ось, на которой pandas должна искать метки:

>>> df.drop(columns=to_drop, inplace=True)

Этот синтаксис более интуитивен и читабелен. То, что мы пытаемся сделать здесь, непосредственно очевидно.

Если вы заранее знаете, какие столбцы вы хотите сохранить, другой вариант - передать их в аргумент usecols в pd.read_csv.

Изменение индекса DataFrame

А pandas Index расширяет функциональность массивов NumPy для более универсальной нарезки и маркировки. Во многих случаях в качестве индекса полезно использовать уникальное идентификационное поле данных.

Например, в наборе данных, использованном в предыдущем разделе, можно ожидать, что при поиске записи библиотекарь может ввести уникальный идентификатор (значения в столбце Identifier) для книги:

>>> df['Identifier'].is_unique
True

Заменим существующий индекс на этот столбец с помощью set_index:

>>> df = df.set_index('Identifier')
>>> df.head()
                Place of Publication Date of Publication  \
206                           London         1879 [1878]
216         London; Virtue & Yorston                1868
218                           London                1869
472                           London                1851
480                           London                1857

                        Publisher  \
206              S. Tinsley & Co.
216                  Virtue & Co.
218         Bradbury, Evans & Co.
472                 James Darling
480          Wertheim & Macintosh

                                                        Title     Author  \
206                         Walter Forbes. [A novel.] By A. A      A. A.
216         All for Greed. [A novel. The dedication signed...  A., A. A.
218         Love the Avenger. By the author of “All for Gr...  A., A. A.
472         Welsh Sketches, chiefly ecclesiastical, to the...  A., E. S.
480         [The World in which I live, and my place in it...  A., E. S.

                                                   Flickr URL
206         http://www.flickr.com/photos/britishlibrary/ta...
216         http://www.flickr.com/photos/britishlibrary/ta...
218         http://www.flickr.com/photos/britishlibrary/ta...
472         http://www.flickr.com/photos/britishlibrary/ta...
480         http://www.flickr.com/photos/britishlibrary/ta...

Технические подробности: В отличие от первичных ключей в SQL, pandas Index не гарантирует их уникальность, хотя многие операции индексирования и объединения заметят ускорение во времени выполнения, если они уникальны.

Мы можем получить доступ к каждой записи простым способом с помощью loc[]. Хотя название loc[] может быть не слишком интуитивным, оно позволяет нам делать индексацию на основе меток, которая представляет собой маркировку строки или записи без учета ее положения:

>>> df.loc[206]
Place of Publication                                               London
Date of Publication                                           1879 [1878]
Publisher                                                S. Tinsley & Co.
Title                                   Walter Forbes. [A novel.] By A. A
Author                                                              A. A.
Flickr URL              http://www.flickr.com/photos/britishlibrary/ta...
Name: 206, dtype: object

Другими словами, 206 - это первая метка индекса. Чтобы получить к ней доступ по позиции, мы могли бы использовать df.iloc[0], который делает индексацию на основе позиции.

Технические подробности: .loc[] технически является экземпляром класса и имеет некоторый специальный синтаксис, который не совсем соответствует большинству простых методов экземпляра в Python.

Ранее наш индекс представлял собой RangeIndex: целые числа, начиная с 0, аналогично встроенному в Python range. Передав имя столбца в set_index, мы изменили индекс на значения в Identifier.

Вы, наверное, заметили, что мы переназначили переменную на объект, возвращаемый методом с помощью df = df.set_index(...). Это происходит потому, что по умолчанию метод возвращает измененную копию нашего объекта и не вносит изменения непосредственно в объект. Мы можем избежать этого, задав параметр inplace:

df.set_index('Identifier', inplace=True)

Уборка полей в данных

На данный момент мы удалили ненужные столбцы и изменили индекс нашего DataFrame на более разумный. В этом разделе мы очистим конкретные столбцы и приведем их к единому формату, чтобы лучше понять набор данных и обеспечить согласованность. В частности, мы очистим Date of Publication и Place of Publication.

При осмотре оказывается, что все типы данных в настоящее время имеют вид object dtype, что примерно аналогично str в родном Python.

В нем содержатся любые поля, которые не могут быть аккуратно оформлены как числовые или категориальные данные. Это имеет смысл, поскольку мы работаем с данными, которые изначально представляют собой кучу беспорядочных строк:

>>> df.get_dtype_counts()
object    6

Одно из полей, где имеет смысл использовать числовое значение, - это дата публикации, чтобы в дальнейшем можно было производить расчеты:

>>> df.loc[1905:, 'Date of Publication'].head(10)
Identifier
1905           1888
1929    1839, 38-54
2836        [1897?]
2854           1865
2956        1860-63
2957           1873
3017           1866
3131           1899
4598           1814
4884           1820
Name: Date of Publication, dtype: object

У конкретной книги может быть только одна дата публикации. Поэтому нам нужно сделать следующее:

  • Уберите лишние даты в квадратных скобках, где они присутствуют: 1879 [1878]
  • Преобразуйте диапазоны дат к их "начальной дате", где бы они ни присутствовали: 1860-63; 1839, 38-54
  • Полностью удалите даты, в которых мы не уверены, и замените их датами NumPy NaN: [1897?]
  • Преобразуйте строку nan в значение NaN от NumPy

Синтезируя эти шаблоны, мы можем воспользоваться одним регулярным выражением для извлечения года публикации:

regex = r'^(\d{4})'

Приведенное выше регулярное выражение предназначено для поиска любых четырех цифр в начале строки, чего вполне достаточно для нашего случая. Приведенное выше выражение представляет собой сырую строку (это означает, что обратная косая черта больше не является управляющим символом), что является стандартной практикой для регулярных выражений.

\d представляет собой любую цифру, а {4} повторяет это правило четыре раза. Символ ^ соответствует началу строки, а круглые скобки обозначают группу захвата, которая сигнализирует pandas, что мы хотим извлечь эту часть regex. (Мы хотим использовать ^, чтобы избежать случаев, когда [ начинается со строки.)

Давайте посмотрим, что произойдет, если мы запустим этот регекс в нашем наборе данных:

>>> extr = df['Date of Publication'].str.extract(r'^(\d{4})', expand=False)
>>> extr.head()
Identifier
206    1879
216    1868
218    1869
472    1851
480    1857
Name: Date of Publication, dtype: object

Дальнейшее чтение: Не знакомы с regex? Вы можете просмотреть выражение выше на сайте regex101.com и узнать все о регулярных выражениях с помощью Regular Expressions: Regexes in Python.

Технически этот столбец по-прежнему имеет dtype object, но мы можем легко получить его числовую версию с помощью pd.to_numeric:

>>> df['Date of Publication'] = pd.to_numeric(extr)
>>> df['Date of Publication'].dtype
dtype('float64')

Это приводит к тому, что примерно каждое десятое значение отсутствует, что является небольшой платой за то, что теперь мы можем производить вычисления для оставшихся допустимых значений:

>>> df['Date of Publication'].isnull().sum() / len(df)
0.11717147339205986

Великолепно! Готово!

Комбинирование str методов с NumPy для очистки столбцов

Выше вы, возможно, заметили использование df['Date of Publication'].str. Этот атрибут - способ доступа к быстрым строковым операциям в pandas, которые во многом имитируют операции над родными строками Python или скомпилированными регулярными выражениями, такими как .split(), .replace() и .capitalize().

Для очистки поля Place of Publication мы можем объединить методы pandas str с функцией NumPy np.where, которая, по сути, является векторной формой макроса Excel IF(). Она имеет следующий синтаксис:

>>> np.where(condition, then, else)

Здесь condition - это либо объект типа массива, либо маска Boolean. then - это значение, которое будет использоваться, если condition оценивается как True, а else - значение, которое будет использоваться в противном случае.

По сути, .where() берет каждый элемент объекта, используемого для condition, проверяет, оценивает ли этот элемент в True в контексте условия, и возвращает ndarray, содержащий then или else, в зависимости от того, что применяется.

Он может быть вложен в составной оператор if-then, что позволяет нам вычислять значения на основе нескольких условий:

>>> np.where(condition1, x1, 
        np.where(condition2, x2, 
            np.where(condition3, x3, ...)))

Мы будем использовать эти две функции для очистки Place of Publication, поскольку в этом столбце есть строковые объекты. Вот содержимое столбца:

>>> df['Place of Publication'].head(10)
Identifier
206                                  London
216                London; Virtue & Yorston
218                                  London
472                                  London
480                                  London
481                                  London
519                                  London
667     pp. 40. G. Bryan & Co: Oxford, 1898
874                                 London]
1143                                 London
Name: Place of Publication, dtype: object

Мы видим, что для некоторых строк место публикации окружено другой ненужной информацией. Если бы мы рассмотрели больше значений, то увидели бы, что это относится только к некоторым строкам, в которых место публикации указано как "Лондон" или "Оксфорд".

Давайте рассмотрим две конкретные записи:

>>> df.loc[4157862]
Place of Publication                                  Newcastle-upon-Tyne
Date of Publication                                                  1867
Publisher                                                      T. Fordyce
Title                   Local Records; or, Historical Register of rema...
Author                                                        T.  Fordyce
Flickr URL              http://www.flickr.com/photos/britishlibrary/ta...
Name: 4157862, dtype: object

>>> df.loc[4159587]
Place of Publication                                  Newcastle upon Tyne
Date of Publication                                                  1834
Publisher                                                Mackenzie & Dent
Title                   An historical, topographical and descriptive v...
Author                                               E. (Eneas) Mackenzie
Flickr URL              http://www.flickr.com/photos/britishlibrary/ta...
Name: 4159587, dtype: object

Эти две книги были изданы в одном и том же месте, но в одной есть дефисы в названии места, а в другой - нет.

Чтобы очистить этот столбец одним движением, мы можем использовать str.contains(), чтобы получить булеву маску.

Очищаем колонку следующим образом:

>>> pub = df['Place of Publication']
>>> london = pub.str.contains('London')
>>> london[:5]
Identifier
206    True
216    True
218    True
472    True
480    True
Name: Place of Publication, dtype: bool

>>> oxford = pub.str.contains('Oxford')

Мы объединяем их с np.where:

df['Place of Publication'] = np.where(london, 'London',
                                      np.where(oxford, 'Oxford',
                                               pub.str.replace('-', ' ')))

>>> df['Place of Publication'].head()
Identifier
206    London
216    London
218    London
472    London
480    London
Name: Place of Publication, dtype: object

Здесь функция np.where вызывается во вложенной структуре, где condition является Series из булевых чисел, полученных с помощью str.contains(). Метод contains() работает аналогично встроенному ключевому слову in, используемому для поиска вхождения сущности в итерабельную переменную (или подстроку в строке).

Замена, которая будет использоваться, - это строка, представляющая желаемое место публикации. Мы также заменяем дефисы пробелом с помощью str.replace() и переназначаем на колонку в нашем DataFrame.

Хотя в этом наборе данных есть и другие грязные данные, мы пока обсудим только эти два столбца.

Давайте посмотрим на первые пять записей, которые выглядят намного лучше, чем когда мы начинали:

>>> df.head()
           Place of Publication Date of Publication              Publisher  \
206                      London                1879        S. Tinsley & Co.
216                      London                1868           Virtue & Co.
218                      London                1869  Bradbury, Evans & Co.
472                      London                1851          James Darling
480                      London                1857   Wertheim & Macintosh

                                                        Title    Author  \
206                         Walter Forbes. [A novel.] By A. A        AA
216         All for Greed. [A novel. The dedication signed...   A. A A.
218         Love the Avenger. By the author of “All for Gr...   A. A A.
472         Welsh Sketches, chiefly ecclesiastical, to the...   E. S A.
480         [The World in which I live, and my place in it...   E. S A.

                                                   Flickr URL
206         http://www.flickr.com/photos/britishlibrary/ta...
216         http://www.flickr.com/photos/britishlibrary/ta...
218         http://www.flickr.com/photos/britishlibrary/ta...
472         http://www.flickr.com/photos/britishlibrary/ta...
480         http://www.flickr.com/photos/britishlibrary/ta...

Примечание: На данном этапе Place of Publication был бы хорошим кандидатом для преобразования в Categorical dtype, потому что мы можем закодировать довольно небольшой уникальный набор городов целыми числами. ( Использование памяти Categorical пропорционально количеству категорий плюс длина данных; объектный dtype - это константа, умноженная на длину данных.)

Очистка всего набора данных с помощью функции applymap

В некоторых ситуациях вы увидите, что "грязь" не локализована на одной колонке, а более разбросана.

В некоторых случаях полезно применить настраиваемую функцию к каждой ячейке или элементу DataFrame. Метод pandas .applymap() аналогичен встроенной map() функции и просто применяет функцию ко всем элементам в DataFrame.

Давайте рассмотрим пример. Мы создадим DataFrame из файла "university_towns.txt":

$ head Datasets/univerisity_towns.txt
Alabama[edit]
Auburn (Auburn University)[1]
Florence (University of North Alabama)
Jacksonville (Jacksonville State University)[2]
Livingston (University of West Alabama)[2]
Montevallo (University of Montevallo)[2]
Troy (Troy University)[2]
Tuscaloosa (University of Alabama, Stillman College, Shelton State)[3][4]
Tuskegee (Tuskegee University)[5]
Alaska[edit]

Мы видим, что периодически за названиями штатов следуют университетские города в этом штате: StateA TownA1 TownA2 StateB TownB1 TownB2.... Если мы посмотрим на то, как записаны названия штатов в файле, то увидим, что все они содержат подстроку "[edit]".

Мы можем воспользоваться этим шаблоном, создав список (state, city) кортежей и обернув этот список в DataFrame:

>>> university_towns = []
>>> with open('Datasets/university_towns.txt') as file:
...     for line in file:
...         if '[edit]' in line:
...             # Remember this `state` until the next is found
...             state = line
...         else:
...             # Otherwise, we have a city; keep `state` as last-seen
...             university_towns.append((state, line))

>>> university_towns[:5]
[('Alabama[edit]\n', 'Auburn (Auburn University)[1]\n'),
 ('Alabama[edit]\n', 'Florence (University of North Alabama)\n'),
 ('Alabama[edit]\n', 'Jacksonville (Jacksonville State University)[2]\n'),
 ('Alabama[edit]\n', 'Livingston (University of West Alabama)[2]\n'),
 ('Alabama[edit]\n', 'Montevallo (University of Montevallo)[2]\n')]

Мы можем обернуть этот список в DataFrame и задать столбцы как "State" и "RegionName". pandas возьмет каждый элемент списка и установит State в левое значение, а RegionName - в правое.

Результирующий DataFrame выглядит следующим образом:

>>> towns_df = pd.DataFrame(university_towns,
...                         columns=['State', 'RegionName'])

>>> towns_df.head()
 State                                         RegionName
0  Alabama[edit]\n                    Auburn (Auburn University)[1]\n
1  Alabama[edit]\n           Florence (University of North Alabama)\n
2  Alabama[edit]\n  Jacksonville (Jacksonville State University)[2]\n
3  Alabama[edit]\n       Livingston (University of West Alabama)[2]\n
4  Alabama[edit]\n         Montevallo (University of Montevallo)[2]\n

Хотя мы могли бы очистить эти строки в цикле for выше, pandas делает это проще. Нам нужны только название штата и название города, а все остальное мы можем удалить. Хотя мы могли бы снова использовать здесь методы pandas .str(), мы также могли бы использовать applymap() для сопоставления вызываемого файла Python с каждым элементом DataFrame.

Мы использовали термин элемент, но что именно мы под ним понимаем? Рассмотрим следующий "игрушечный" DataFrame:

        0           1
0    Mock     Dataset
1  Python     pandas
2    Real     Python
3   NumPy     Clean

В данном примере каждая ячейка ('Mock', 'Dataset', 'Python', 'pandas' и т. д.) является элементом. Поэтому applymap() будет применять функцию к каждому из них независимо. Давайте определим эту функцию:

>>> def get_citystate(item):
...     if ' (' in item:
...         return item[:item.find(' (')]
...     elif '[' in item:
...         return item[:item.find('[')]
...     else:
...         return item

pandas' .applymap() принимает только один параметр, который является функцией (вызываемой), которая должна быть применена к каждому элементу:

>>> towns_df =  towns_df.applymap(get_citystate)

Сначала мы определяем функцию Python, которая принимает в качестве параметра элемент из DataFrame. Внутри функции выполняются проверки, чтобы определить, есть ли в элементе ( или [ или нет.

В зависимости от проверки функция возвращает соответствующие значения. Наконец, функция applymap() вызывается на нашем объекте. Теперь DataFrame выглядит гораздо аккуратнее:

>>> towns_df.head()
     State    RegionName
0  Alabama        Auburn
1  Alabama      Florence
2  Alabama  Jacksonville
3  Alabama    Livingston
4  Alabama    Montevallo

Метод applymap() брал каждый элемент из DataFrame, передавал его в функцию, и исходное значение заменялось возвращаемым значением. Все просто!

Технические подробности: Несмотря на удобство и универсальность метода, .applymap может иметь значительное время выполнения для больших наборов данных, поскольку он сопоставляет Python callable с каждым отдельным элементом. В некоторых случаях эффективнее выполнять векторизованные операции, использующие Cython или NumPY (которые, в свою очередь, выполняют вызовы на C) под капотом.

Переименование столбцов и пропуск строк

Часто наборы данных, с которыми вы будете работать, содержат либо непонятные названия столбцов, либо неважную информацию в первых и/или последних строках, например, определения терминов, используемых в наборе данных, или сноски.

В этом случае мы захотим переименовать столбцы и пропустить некоторые строки, чтобы можно было дойти до нужной информации с правильными и разумными метками.

Чтобы продемонстрировать, как мы можем это сделать, давайте сначала посмотрим на первые пять строк набора данных "olympics.csv":

$ head -n 5 Datasets/olympics.csv
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
,? Summer,01 !,02 !,03 !,Total,? Winter,01 !,02 !,03 !,Total,? Games,01 !,02 !,03 !,Combined total
Afghanistan (AFG),13,0,0,2,2,0,0,0,0,0,13,0,0,2,2
Algeria (ALG),12,5,2,8,15,3,0,0,0,0,15,5,2,8,15
Argentina (ARG),23,18,24,28,70,18,0,0,0,0,41,18,24,28,70

Теперь прочитаем его в pandas DataFrame:

>>> olympics_df = pd.read_csv('Datasets/olympics.csv')
>>> olympics_df.head()
                   0         1     2     3     4      5         6     7     8  \
0                NaN  ? Summer  01 !  02 !  03 !  Total  ? Winter  01 !  02 !
1  Afghanistan (AFG)        13     0     0     2      2         0     0     0
2      Algeria (ALG)        12     5     2     8     15         3     0     0
3    Argentina (ARG)        23    18    24    28     70        18     0     0
4      Armenia (ARM)         5     1     2     9     12         6     0     0

      9     10       11    12    13    14              15
0  03 !  Total  ? Games  01 !  02 !  03 !  Combined total
1     0      0       13     0     0     2               2
2     0      0       15     5     2     8              15
3     0      0       41    18    24    28              70
4     0      0       11     1     2     9              12

Это действительно грязно! Столбцы представляют собой строковую форму целых чисел, индексированных по 0. Строка, которая должна была быть нашим заголовком (т. е. использоваться для задания имен столбцов), находится по адресу olympics_df.iloc[0]. Это произошло потому, что наш CSV-файл начинается с 0, 1, 2, ..., 15.

Кроме того, если мы обратимся к источнику этого набора данных, то увидим, что NaN выше должно быть что-то вроде "Страна", ? Summer должно представлять "Летние игры", 01 ! должно быть "Золото", и так далее.

Поэтому нам нужно сделать две вещи:

  • Пропустите одну строку и установите заголовок в качестве первой (с индексом 0) строки
  • Переименуйте столбцы

Мы можем пропускать строки и устанавливать заголовок при чтении CSV-файла, передавая некоторые параметры в функцию read_csv().

Эта функция принимает много необязательных параметров, но в данном случае нам нужен только один (header), чтобы удалить 0-й ряд:

>>> olympics_df = pd.read_csv('Datasets/olympics.csv', header=1)
>>> olympics_df.head()
          Unnamed: 0  ? Summer  01 !  02 !  03 !  Total  ? Winter  \
0        Afghanistan (AFG)        13     0     0     2      2         0
1            Algeria (ALG)        12     5     2     8     15         3
2          Argentina (ARG)        23    18    24    28     70        18
3            Armenia (ARM)         5     1     2     9     12         6
4  Australasia (ANZ) [ANZ]         2     3     4     5     12         0

   01 !.1  02 !.1  03 !.1  Total.1  ? Games  01 !.2  02 !.2  03 !.2  \
0       0       0       0        0       13       0       0       2
1       0       0       0        0       15       5       2       8
2       0       0       0        0       41      18      24      28
3       0       0       0        0       11       1       2       9
4       0       0       0        0        2       3       4       5

   Combined total
0               2
1              15
2              70
3              12
4              12

Теперь у нас есть правильная строка, установленная в качестве заголовка, и все ненужные строки удалены. Обратите внимание на то, как pandas изменила название столбца, содержащего название стран, с NaN на Unnamed: 0.

Для переименования столбцов мы воспользуемся методом rename() DataFrame, который позволяет перемаркировать ось на основе отображения (в данном случае dict).

Начнем с определения словаря, который сопоставляет текущие имена столбцов (в качестве ключей) с более удобными (значениями словаря):

>>> new_names =  {'Unnamed: 0': 'Country',
...               '? Summer': 'Summer Olympics',
...               '01 !': 'Gold',
...               '02 !': 'Silver',
...               '03 !': 'Bronze',
...               '? Winter': 'Winter Olympics',
...               '01 !.1': 'Gold.1',
...               '02 !.1': 'Silver.1',
...               '03 !.1': 'Bronze.1',
...               '? Games': '# Games',
...               '01 !.2': 'Gold.2',
...               '02 !.2': 'Silver.2',
...               '03 !.2': 'Bronze.2'}

Мы вызываем функцию rename() на нашем объекте:

>>> olympics_df.rename(columns=new_names, inplace=True)

Установка inplace в значение True указывает, что наши изменения будут внесены непосредственно в объект. Давайте проверим, насколько это соответствует действительности:

>>> olympics_df.head()
                   Country  Summer Olympics  Gold  Silver  Bronze  Total  \
0        Afghanistan (AFG)               13     0       0       2      2
1            Algeria (ALG)               12     5       2       8     15
2          Argentina (ARG)               23    18      24      28     70
3            Armenia (ARM)                5     1       2       9     12
4  Australasia (ANZ) [ANZ]                2     3       4       5     12

   Winter Olympics  Gold.1  Silver.1  Bronze.1  Total.1  # Games  Gold.2  \
0                0       0         0         0        0       13       0
1                3       0         0         0        0       15       5
2               18       0         0         0        0       41      18
3                6       0         0         0        0       11       1
4                0       0         0         0        0        2       3

   Silver.2  Bronze.2  Combined total
0         0         2               2
1         2         8              15
2        24        28              70
3         2         9              12
4         4         5              12

Очистка данных на языке Python: Обзор и ресурсы

В этом уроке вы узнали, как можно удалить ненужную информацию из набора данных с помощью функции drop(), а также как задать индекс для набора данных, чтобы на элементы в нем можно было легко ссылаться.

Кроме того, вы узнали, как очищать object поля с помощью аксессора .str() и как очищать весь набор данных с помощью метода applymap(). Наконец, мы изучили, как пропускать строки в CSV-файле и переименовывать столбцы с помощью метода rename().

Знать об очистке данных очень важно, потому что это большая часть науки о данных. Теперь вы имеете базовое представление о том, как можно использовать pandas и NumPy для очистки наборов данных!

По ссылкам ниже вы найдете дополнительные ресурсы, которые помогут вам на пути к науке о данных на Python:

Вернуться на верх