Анализ данных с помощью pandas. Часть 6: работа с загрязненными данными
Главная проблема загрязненных данных: понять, они загрязнены или нет?
Используем данные NYC 311 service request из одной из прошлых статей, так как их много и они неочевидны.
In [1]:%matplotlib inline
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
pd.options.display.max_rows = 7
plt.style.use('ggplot')
plt.rcParams['figure.figsize'] = (15, 3)
plt.rcParams['font.family'] = 'sans-serif'requests = pd.read_csv('data/311-service-requests.csv')/usr/local/lib/python3.5/dist-packages/IPython/core/interactiveshell.py:2683: DtypeWarning: Columns (8) have mixed types. Specify dtype option on import or set low_memory=False. interactivity=interactivity, compiler=compiler, result=result)
Как узнать, что данные загрязнены?
Посмотрим на некоторые колонки. Есть некоторые проблемы с zip code, поэтому посмотрим сначала на него.
Чтобы предположить, есть ли в колонке проблема, можно использовать .unique() для просмотра всех значений. Если это численные данные, вместо этого можно построить гистограмму значений, чтобы понять распределение данных.
Когда мы посмотрим на уникальные значения в "Incident Zip", станет сразу ясно, что это мусор. Некоторые проблемы:
- Некоторые значения - строки, некоторые - числа
- Есть
nan - Некоторые значения
29616-0759или83 - Некоторые неопределённые значения, которые pandas не смог распознать, такие как 'N/A' и 'NO CLUE'
Что можно сделать:
- Преобразовать 'N/A' и 'NO CLUE' в обычные
nan - Посмотреть, что такое
83, и решить, что же делать - Сделать все строками
requests['Incident Zip'].unique()array([11432.0, 11378.0, 10032.0, 10023.0, 10027.0, 11372.0, 11419.0,
11417.0, 10011.0, 11225.0, 11218.0, 10003.0, 10029.0, 10466.0,
11219.0, 10025.0, 10310.0, 11236.0, nan, 10033.0, 11216.0, 10016.0,
10305.0, 10312.0, 10026.0, 10309.0, 10036.0, 11433.0, 11235.0,
11213.0, 11379.0, 11101.0, 10014.0, 11231.0, 11234.0, 10457.0,
10459.0, 10465.0, 11207.0, 10002.0, 10034.0, 11233.0, 10453.0,
10456.0, 10469.0, 11374.0, 11221.0, 11421.0, 11215.0, 10007.0,
10019.0, 11205.0, 11418.0, 11369.0, 11249.0, 10005.0, 10009.0,
11211.0, 11412.0, 10458.0, 11229.0, 10065.0, 10030.0, 11222.0,
10024.0, 10013.0, 11420.0, 11365.0, 10012.0, 11214.0, 11212.0,
10022.0, 11232.0, 11040.0, 11226.0, 10281.0, 11102.0, 11208.0,
10001.0, 10472.0, 11414.0, 11223.0, 10040.0, 11220.0, 11373.0,
11203.0, 11691.0, 11356.0, 10017.0, 10452.0, 10280.0, 11217.0,
10031.0, 11201.0, 11358.0, 10128.0, 11423.0, 10039.0, 10010.0,
11209.0, 10021.0, 10037.0, 11413.0, 11375.0, 11238.0, 10473.0,
11103.0, 11354.0, 11361.0, 11106.0, 11385.0, 10463.0, 10467.0,
11204.0, 11237.0, 11377.0, 11364.0, 11434.0, 11435.0, 11210.0,
11228.0, 11368.0, 11694.0, 10464.0, 11415.0, 10314.0, 10301.0,
10018.0, 10038.0, 11105.0, 11230.0, 10468.0, 11104.0, 10471.0,
11416.0, 10075.0, 11422.0, 11355.0, 10028.0, 10462.0, 10306.0,
10461.0, 11224.0, 11429.0, 10035.0, 11366.0, 11362.0, 11206.0,
10460.0, 10304.0, 11360.0, 11411.0, 10455.0, 10475.0, 10069.0,
10303.0, 10308.0, 10302.0, 11357.0, 10470.0, 11367.0, 11370.0,
10454.0, 10451.0, 11436.0, 11426.0, 10153.0, 11004.0, 11428.0,
11427.0, 11001.0, 11363.0, 10004.0, 10474.0, 11430.0, 10000.0,
10307.0, 11239.0, 10119.0, 10006.0, 10048.0, 11697.0, 11692.0,
11693.0, 10573.0, 83.0, 11559.0, 10020.0, 77056.0, 11776.0, 70711.0,
10282.0, 11109.0, 10044.0, '10452', '11233', '10468', '10310',
'11105', '10462', '10029', '10301', '10457', '10467', '10469',
'11225', '10035', '10031', '11226', '10454', '11221', '10025',
'11229', '11235', '11422', '10472', '11208', '11102', '10032',
'11216', '10473', '10463', '11213', '10040', '10302', '11231',
'10470', '11204', '11104', '11212', '10466', '11416', '11214',
'10009', '11692', '11385', '11423', '11201', '10024', '11435',
'10312', '10030', '11106', '10033', '10303', '11215', '11222',
'11354', '10016', '10034', '11420', '10304', '10019', '11237',
'11249', '11230', '11372', '11207', '11378', '11419', '11361',
'10011', '11357', '10012', '11358', '10003', '10002', '11374',
'10007', '11234', '10065', '11369', '11434', '11205', '11206',
'11415', '11236', '11218', '11413', '10458', '11101', '10306',
'11355', '10023', '11368', '10314', '11421', '10010', '10018',
'11223', '10455', '11377', '11433', '11375', '10037', '11209',
'10459', '10128', '10014', '10282', '11373', '10451', '11238',
'11211', '10038', '11694', '11203', '11691', '11232', '10305',
'10021', '11228', '10036', '10001', '10017', '11217', '11219',
'10308', '10465', '11379', '11414', '10460', '11417', '11220',
'11366', '10027', '11370', '10309', '11412', '11356', '10456',
'11432', '10022', '10013', '11367', '11040', '10026', '10475',
'11210', '11364', '11426', '10471', '10119', '11224', '11418',
'11429', '11365', '10461', '11239', '10039', '00083', '11411',
'10075', '11004', '11360', '10453', '10028', '11430', '10307',
'11103', '10004', '10069', '10005', '10474', '11428', '11436',
'10020', '11001', '11362', '11693', '10464', '11427', '10044',
'11363', '10006', '10000', '02061', '77092-2016', '10280', '11109',
'14225', '55164-0737', '19711', '07306', '000000', 'NO CLUE',
'90010', '10281', '11747', '23541', '11776', '11697', '11788',
'07604', 10112.0, 11788.0, 11563.0, 11580.0, 7087.0, 11042.0,
7093.0, 11501.0, 92123.0, 0.0, 11575.0, 7109.0, 11797.0, '10803',
'11716', '11722', '11549-3650', '10162', '92123', '23502', '11518',
'07020', '08807', '11577', '07114', '11003', '07201', '11563',
'61702', '10103', '29616-0759', '35209-3114', '11520', '11735',
'10129', '11005', '41042', '11590', 6901.0, 7208.0, 11530.0,
13221.0, 10954.0, 11735.0, 10103.0, 7114.0, 11111.0, 10107.0], dtype=object)Исправление ошибок с NAN и различий строки/числа
Можно передать na_values в pd.read_csv, чтобы немного очистить данные. Также можно явно указать тип для Incident Zip.
na_values = ['NO CLUE', 'N/A', '0']
requests = pd.read_csv('data/311-service-requests.csv', na_values=na_values, dtype={'Incident Zip': str})requests['Incident Zip'].unique()array(['11432', '11378', '10032', '10023', '10027', '11372', '11419',
'11417', '10011', '11225', '11218', '10003', '10029', '10466',
'11219', '10025', '10310', '11236', nan, '10033', '11216', '10016',
'10305', '10312', '10026', '10309', '10036', '11433', '11235',
'11213', '11379', '11101', '10014', '11231', '11234', '10457',
'10459', '10465', '11207', '10002', '10034', '11233', '10453',
'10456', '10469', '11374', '11221', '11421', '11215', '10007',
'10019', '11205', '11418', '11369', '11249', '10005', '10009',
'11211', '11412', '10458', '11229', '10065', '10030', '11222',
'10024', '10013', '11420', '11365', '10012', '11214', '11212',
'10022', '11232', '11040', '11226', '10281', '11102', '11208',
'10001', '10472', '11414', '11223', '10040', '11220', '11373',
'11203', '11691', '11356', '10017', '10452', '10280', '11217',
'10031', '11201', '11358', '10128', '11423', '10039', '10010',
'11209', '10021', '10037', '11413', '11375', '11238', '10473',
'11103', '11354', '11361', '11106', '11385', '10463', '10467',
'11204', '11237', '11377', '11364', '11434', '11435', '11210',
'11228', '11368', '11694', '10464', '11415', '10314', '10301',
'10018', '10038', '11105', '11230', '10468', '11104', '10471',
'11416', '10075', '11422', '11355', '10028', '10462', '10306',
'10461', '11224', '11429', '10035', '11366', '11362', '11206',
'10460', '10304', '11360', '11411', '10455', '10475', '10069',
'10303', '10308', '10302', '11357', '10470', '11367', '11370',
'10454', '10451', '11436', '11426', '10153', '11004', '11428',
'11427', '11001', '11363', '10004', '10474', '11430', '10000',
'10307', '11239', '10119', '10006', '10048', '11697', '11692',
'11693', '10573', '00083', '11559', '10020', '77056', '11776',
'70711', '10282', '11109', '10044', '02061', '77092-2016', '14225',
'55164-0737', '19711', '07306', '000000', '90010', '11747', '23541',
'11788', '07604', '10112', '11563', '11580', '07087', '11042',
'07093', '11501', '92123', '00000', '11575', '07109', '11797',
'10803', '11716', '11722', '11549-3650', '10162', '23502', '11518',
'07020', '08807', '11577', '07114', '11003', '07201', '61702',
'10103', '29616-0759', '35209-3114', '11520', '11735', '10129',
'11005', '41042', '11590', '06901', '07208', '11530', '13221',
'10954', '11111', '10107'], dtype=object)Что с дефисами?
In [6]:rows_with_dashes = requests['Incident Zip'].str.contains('-').fillna(False)
len(requests[rows_with_dashes])5In [7]:
requests[rows_with_dashes]['Incident Zip']29136 77092-2016 30939 55164-0737 70539 11549-3650 85821 29616-0759 89304 35209-3114 Name: Incident Zip, dtype: object
Сначала родилось предположение о том, что это пропущенные данные, и эти строки нужно удалить:
requests['Incident Zip'][rows_with_dashes] = np.nan
Но оказалось, что zip code из 9 цифр - это норма. Посмотрим на них, поймём, всё ли правильно, и обрежем их.
In [8]:long_zip_codes = requests['Incident Zip'].str.len() > 5
requests['Incident Zip'][long_zip_codes].unique()array(['77092-2016', '55164-0737', '000000', '11549-3650', '29616-0759',
'35209-3114'], dtype=object) In [9]: requests['Incident Zip'] = requests['Incident Zip'].str.slice(0, 5)Было предположение, что 00083 - это неверный zip code, но оказалось, что он принадлежит Central Park! Посмотрим на код 00000:
In [10]:requests[requests['Incident Zip'] == '00000']| Unique Key | Created Date | Closed Date | Agency | Agency Name | Complaint Type | Descriptor | Location Type | Incident Zip | Incident Address | ... | Bridge Highway Name | Bridge Highway Direction | Road Ramp | Bridge Highway Segment | Garage Lot Name | Ferry Direction | Ferry Terminal Name | Latitude | Longitude | Location | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 42600 | 26529313 | 10/22/2013 02:51:06 PM | NaN | TLC | Taxi and Limousine Commission | Taxi Complaint | Driver Complaint | NaN | 00000 | EWR EWR | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 60843 | 26507389 | 10/17/2013 05:48:44 PM | NaN | TLC | Taxi and Limousine Commission | Taxi Complaint | Driver Complaint | Street | 00000 | 1 NEWARK AIRPORT | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
2 rows × 52 columns
Выглядит удручающе. Заменим на nan.
zero_zips = requests['Incident Zip'] == '00000'
requests.loc[zero_zips, 'Incident Zip'] = np.nanПосмотрим, чего мы добились:
In [12]:unique_zips = requests['Incident Zip'].unique()
unique_zipsarray(['11432', '11378', '10032', '10023', '10027', '11372', '11419',
'11417', '10011', '11225', '11218', '10003', '10029', '10466',
'11219', '10025', '10310', '11236', nan, '10033', '11216', '10016',
'10305', '10312', '10026', '10309', '10036', '11433', '11235',
'11213', '11379', '11101', '10014', '11231', '11234', '10457',
'10459', '10465', '11207', '10002', '10034', '11233', '10453',
'10456', '10469', '11374', '11221', '11421', '11215', '10007',
'10019', '11205', '11418', '11369', '11249', '10005', '10009',
'11211', '11412', '10458', '11229', '10065', '10030', '11222',
'10024', '10013', '11420', '11365', '10012', '11214', '11212',
'10022', '11232', '11040', '11226', '10281', '11102', '11208',
'10001', '10472', '11414', '11223', '10040', '11220', '11373',
'11203', '11691', '11356', '10017', '10452', '10280', '11217',
'10031', '11201', '11358', '10128', '11423', '10039', '10010',
'11209', '10021', '10037', '11413', '11375', '11238', '10473',
'11103', '11354', '11361', '11106', '11385', '10463', '10467',
'11204', '11237', '11377', '11364', '11434', '11435', '11210',
'11228', '11368', '11694', '10464', '11415', '10314', '10301',
'10018', '10038', '11105', '11230', '10468', '11104', '10471',
'11416', '10075', '11422', '11355', '10028', '10462', '10306',
'10461', '11224', '11429', '10035', '11366', '11362', '11206',
'10460', '10304', '11360', '11411', '10455', '10475', '10069',
'10303', '10308', '10302', '11357', '10470', '11367', '11370',
'10454', '10451', '11436', '11426', '10153', '11004', '11428',
'11427', '11001', '11363', '10004', '10474', '11430', '10000',
'10307', '11239', '10119', '10006', '10048', '11697', '11692',
'11693', '10573', '00083', '11559', '10020', '77056', '11776',
'70711', '10282', '11109', '10044', '02061', '77092', '14225',
'55164', '19711', '07306', '90010', '11747', '23541', '11788',
'07604', '10112', '11563', '11580', '07087', '11042', '07093',
'11501', '92123', '11575', '07109', '11797', '10803', '11716',
'11722', '11549', '10162', '23502', '11518', '07020', '08807',
'11577', '07114', '11003', '07201', '61702', '10103', '29616',
'35209', '11520', '11735', '10129', '11005', '41042', '11590',
'06901', '07208', '11530', '13221', '10954', '11111', '10107'], dtype=object)Здорово! Гораздо чище. Хотя немного странно: zip code 77056, согласно Google maps, принадлежит Техасу.
Посмотрим поближе:
In [13]:zips = requests['Incident Zip']
# Let's say the zips starting with '0' and '1' are okay, for now. (this isn't actually true -- 13221 is in Syracuse, and why?)
is_close = zips.str.startswith('0') | zips.str.startswith('1')
# There are a bunch of NaNs, but we're not interested in them right now, so we'll say they're False
is_far = ~(is_close) & zips.notnull()zips[is_far]12102 77056
13450 70711
29136 77092
...
85821 29616
89304 35209
94201 41042
Name: Incident Zip, dtype: object In [15]: requests[is_far][['Incident Zip', 'Descriptor', 'City']].sort_values('Incident Zip')| Incident Zip | Descriptor | City | |
|---|---|---|---|
| 71834 | 23502 | Harassment | NORFOLK |
| 47048 | 23541 | Harassment | NORFOLK |
| 85821 | 29616 | Debt Not Owed | GREENVILLE |
| ... | ... | ... | ... |
| 44008 | 90010 | Billing Dispute | LOS ANGELES |
| 57636 | 92123 | Harassment | SAN DIEGO |
| 71001 | 92123 | Billing Dispute | SAN DIEGO |
13 rows × 3 columns
О, Лос Анджелес! Фильтрация по zip code не лучший путь для обработки таких ситуаций - гораздо лучше сразу посмотреть на город.
In [16]:requests['City'].str.upper().value_counts()BROOKLYN 31662
NEW YORK 22664
BRONX 18438
...
MELVILLE 1
LAWRENCE 1
BOHIEMA 1
Name: City, dtype: int64

LETPY.COM