import pandas as pd
import numpy as np
import seaborn as sns
import plotly.express as px
import matplotlib.pyplot as plt
from matplotlib import pyplot as plt
%matplotlib inline
try:
data = pd.read_csv('/datasets/data.csv')
except:
data = pd.read_csv('https://code.s3.yandex.net/datasets/data.csv')
Задание 2. Выведите первые 20 строчек датафрейма data
на экран.
data.head(20)
children | days_employed | dob_years | education | education_id | family_status | family_status_id | gender | income_type | debt | total_income | purpose | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | -8437.673028 | 42 | высшее | 0 | женат / замужем | 0 | F | сотрудник | 0 | 253875.639453 | покупка жилья |
1 | 1 | -4024.803754 | 36 | среднее | 1 | женат / замужем | 0 | F | сотрудник | 0 | 112080.014102 | приобретение автомобиля |
2 | 0 | -5623.422610 | 33 | Среднее | 1 | женат / замужем | 0 | M | сотрудник | 0 | 145885.952297 | покупка жилья |
3 | 3 | -4124.747207 | 32 | среднее | 1 | женат / замужем | 0 | M | сотрудник | 0 | 267628.550329 | дополнительное образование |
4 | 0 | 340266.072047 | 53 | среднее | 1 | гражданский брак | 1 | F | пенсионер | 0 | 158616.077870 | сыграть свадьбу |
5 | 0 | -926.185831 | 27 | высшее | 0 | гражданский брак | 1 | M | компаньон | 0 | 255763.565419 | покупка жилья |
6 | 0 | -2879.202052 | 43 | высшее | 0 | женат / замужем | 0 | F | компаньон | 0 | 240525.971920 | операции с жильем |
7 | 0 | -152.779569 | 50 | СРЕДНЕЕ | 1 | женат / замужем | 0 | M | сотрудник | 0 | 135823.934197 | образование |
8 | 2 | -6929.865299 | 35 | ВЫСШЕЕ | 0 | гражданский брак | 1 | F | сотрудник | 0 | 95856.832424 | на проведение свадьбы |
9 | 0 | -2188.756445 | 41 | среднее | 1 | женат / замужем | 0 | M | сотрудник | 0 | 144425.938277 | покупка жилья для семьи |
10 | 2 | -4171.483647 | 36 | высшее | 0 | женат / замужем | 0 | M | компаньон | 0 | 113943.491460 | покупка недвижимости |
11 | 0 | -792.701887 | 40 | среднее | 1 | женат / замужем | 0 | F | сотрудник | 0 | 77069.234271 | покупка коммерческой недвижимости |
12 | 0 | NaN | 65 | среднее | 1 | гражданский брак | 1 | M | пенсионер | 0 | NaN | сыграть свадьбу |
13 | 0 | -1846.641941 | 54 | неоконченное высшее | 2 | женат / замужем | 0 | F | сотрудник | 0 | 130458.228857 | приобретение автомобиля |
14 | 0 | -1844.956182 | 56 | высшее | 0 | гражданский брак | 1 | F | компаньон | 1 | 165127.911772 | покупка жилой недвижимости |
15 | 1 | -972.364419 | 26 | среднее | 1 | женат / замужем | 0 | F | сотрудник | 0 | 116820.904450 | строительство собственной недвижимости |
16 | 0 | -1719.934226 | 35 | среднее | 1 | женат / замужем | 0 | F | сотрудник | 0 | 289202.704229 | недвижимость |
17 | 0 | -2369.999720 | 33 | высшее | 0 | гражданский брак | 1 | M | сотрудник | 0 | 90410.586745 | строительство недвижимости |
18 | 0 | 400281.136913 | 53 | среднее | 1 | вдовец / вдова | 2 | F | пенсионер | 0 | 56823.777243 | на покупку подержанного автомобиля |
19 | 0 | -10038.818549 | 48 | СРЕДНЕЕ | 1 | в разводе | 3 | F | сотрудник | 0 | 242831.107982 | на покупку своего автомобиля |
Задание 3. Выведите основную информацию о датафрейме с помощью метода info()
.
data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 21525 entries, 0 to 21524 Data columns (total 12 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 children 21525 non-null int64 1 days_employed 19351 non-null float64 2 dob_years 21525 non-null int64 3 education 21525 non-null object 4 education_id 21525 non-null int64 5 family_status 21525 non-null object 6 family_status_id 21525 non-null int64 7 gender 21525 non-null object 8 income_type 21525 non-null object 9 debt 21525 non-null int64 10 total_income 19351 non-null float64 11 purpose 21525 non-null object dtypes: float64(2), int64(5), object(5) memory usage: 2.0+ MB
Задание 4. Выведите количество пропущенных значений для каждого столбца. Используйте комбинацию двух методов.
data.isna().sum()
children 0 days_employed 2174 dob_years 0 education 0 education_id 0 family_status 0 family_status_id 0 gender 0 income_type 0 debt 0 total_income 2174 purpose 0 dtype: int64
Задание 5. В двух столбцах есть пропущенные значения. Один из них — days_employed
. Пропуски в этом столбце вы обработаете на следующем этапе. Другой столбец с пропущенными значениями — total_income
— хранит данные о доходах. На сумму дохода сильнее всего влияет тип занятости, поэтому заполнить пропуски в этом столбце нужно медианным значением по каждому типу из столбца income_type
. Например, у человека с типом занятости сотрудник
пропуск в столбце total_income
должен быть заполнен медианным доходом среди всех записей с тем же типом.
for i in data['income_type'].unique():
data.loc[(data['income_type'] == i) & (data['total_income'].isna()), 'total_income'] = \
data.loc[(data['income_type'] == i), 'total_income'].median()
Задание 6. В данных могут встречаться артефакты (аномалии) — значения, которые не отражают действительность и появились по какой-то ошибке. таким артефактом будет отрицательное количество дней трудового стажа в столбце days_employed
. Для реальных данных это нормально. Обработайте значения в этом столбце: замените все отрицательные значения положительными с помощью метода abs()
.
data['days_employed'] = data['days_employed'].abs()
Задание 7. Для каждого типа занятости выведите медианное значение трудового стажа days_employed
в днях.
data.groupby('income_type')['days_employed'].agg('median')
income_type безработный 366413.652744 в декрете 3296.759962 госслужащий 2689.368353 компаньон 1547.382223 пенсионер 365213.306266 предприниматель 520.848083 сотрудник 1574.202821 студент 578.751554 Name: days_employed, dtype: float64
Задание 8. Выведите перечень уникальных значений столбца children
.
data['children'].unique()
array([ 1, 0, 3, 2, -1, 4, 20, 5])
Задание 9. В столбце children
есть два аномальных значения. Удалите строки, в которых встречаются такие аномальные значения из датафрейма data
.
data = data[(data['children'] != -1) & (data['children'] != 20)]
Задание 10. Ещё раз выведите перечень уникальных значений столбца children
, чтобы убедиться, что артефакты удалены.
data['children'].unique()
array([1, 0, 3, 2, 4, 5])
Задание 11. Заполните пропуски в столбце days_employed
медианными значениями по каждого типа занятости income_type
.
for i in data['income_type'].unique():
data.loc[(data['income_type'] == i) & (data['days_employed'].isna()), 'days_employed'] = \
data.loc[(data['income_type'] == i), 'days_employed'].median()
Задание 12. Убедитесь, что все пропуски заполнены. Проверьте себя и ещё раз выведите количество пропущенных значений для каждого столбца с помощью двух методов.
data.isna().sum()
children 0 days_employed 0 dob_years 0 education 0 education_id 0 family_status 0 family_status_id 0 gender 0 income_type 0 debt 0 total_income 0 purpose 0 dtype: int64
Задание 13. Замените вещественный тип данных в столбце total_income
на целочисленный с помощью метода astype()
.
data['total_income'] = data['total_income'].astype(int)
Задание 14. Обработайте неявные дубликаты в столбце education
. В этом столбце есть одни и те же значения, но записанные по-разному: с использованием заглавных и строчных букв. Приведите их к нижнему регистру. Проверьте остальные столбцы.
data['education'] = data['education'].str.lower()
Задание 15. Выведите на экран количество строк-дубликатов в данных. Если такие строки присутствуют, удалите их.
data.duplicated().sum()
71
data = data.drop_duplicates()
Задание 16. На основании диапазонов, указанных ниже, создайте в датафрейме data
столбец total_income_category
с категориями:
'E'
;'D'
;'C'
;'B'
;'A'
.Например, кредитополучателю с доходом 25000 нужно назначить категорию 'E'
, а клиенту, получающему 235000, — 'B'
. Используйте собственную функцию с именем categorize_income()
и метод apply()
.
def categorize_income(income):
try:
if 0 <= income <= 50000:
return 'Зарплата до 50 тыс.₽'
elif 50001 <= income <= 100000:
return 'от 50 до 100 тыс. ₽'
elif 100001 <= income <= 150000:
return 'от 100 до 150 тыс. ₽'
elif 150001 <= income <= 200000:
return 'от 150 до 200 тыс. ₽'
elif 200001 <= income <= 250000:
return 'от 200 до 250 тыс. ₽'
elif 250001 <= income <= 300000:
return 'от 250 до 300 тыс. ₽'
elif 300001 <= income <= 350000:
return 'от 300 до 350 тыс. ₽'
elif 350001 <= income <= 400000:
return 'от 350 до 400 тыс. ₽'
elif 400001 <= income <= 450000:
return 'от 400 до 450 тыс. ₽'
elif 450001 <= income <= 500000:
return 'от 450 до 500 тыс. ₽'
elif income >= 500001:
return 'Больше 500 тыс. ₽'
except:
pass
data['total_income_category'] = data['total_income'].apply(categorize_income)
Задание 17. Выведите на экран перечень уникальных целей взятия кредита из столбца purpose
.
data['purpose'].unique()
array(['покупка жилья', 'приобретение автомобиля', 'дополнительное образование', 'сыграть свадьбу', 'операции с жильем', 'образование', 'на проведение свадьбы', 'покупка жилья для семьи', 'покупка недвижимости', 'покупка коммерческой недвижимости', 'покупка жилой недвижимости', 'строительство собственной недвижимости', 'недвижимость', 'строительство недвижимости', 'на покупку подержанного автомобиля', 'на покупку своего автомобиля', 'операции с коммерческой недвижимостью', 'строительство жилой недвижимости', 'жилье', 'операции со своей недвижимостью', 'автомобили', 'заняться образованием', 'сделка с подержанным автомобилем', 'получение образования', 'автомобиль', 'свадьба', 'получение дополнительного образования', 'покупка своего жилья', 'операции с недвижимостью', 'получение высшего образования', 'свой автомобиль', 'сделка с автомобилем', 'профильное образование', 'высшее образование', 'покупка жилья для сдачи', 'на покупку автомобиля', 'ремонт жилью', 'заняться высшим образованием'], dtype=object)
Задание 18. Создайте функцию, которая на основании данных из столбца purpose
сформирует новый столбец purpose_category
, в который войдут следующие категории:
'операции с автомобилем'
,'операции с недвижимостью'
,'проведение свадьбы'
,'получение образования'
.Например, если в столбце purpose
находится подстрока 'на покупку автомобиля'
, то в столбце purpose_category
должна появиться строка 'операции с автомобилем'
.
Используйте собственную функцию с именем categorize_purpose()
и метод apply()
. Изучите данные в столбце purpose
и определите, какие подстроки помогут вам правильно определить категорию.
def categorize_purpose(row):
try:
if 'автом' in row:
return 'операции с автомобилем'
elif 'жил' in row or 'недвиж' in row:
return 'операции с недвижимостью'
elif 'свад' in row:
return 'проведение свадьбы'
elif 'образов' in row:
return 'получение образования'
except:
return 'нет категории'
data['purpose_category'] = data['purpose'].apply(categorize_purpose)
# смотрим уникальные значения столбца children
data.children.value_counts()
0 14091 1 4808 2 2052 3 330 4 41 5 9 Name: children, dtype: int64
# Напишем функицю для разбиения количества детей на категории
def children_category(count):
a = count['children']
try:
if a == 0:
return 'Нет детей'
elif a == 1:
return '1 ребенок'
elif a == 2:
return '2 ребенка'
elif a == 3:
return '3 ребенка'
elif a == 4:
return '4 ребенка'
elif a == 5:
return '5 детей'
return 'Многодетные'
except:
pass
data['children_category'] = data.apply(children_category, axis=1)
# Отсортируем по убыванию и видим предсказуемую закономерность
data.groupby('children_category', as_index=False)['children'].count().sort_values('children', ascending=False)
children_category | children | |
---|---|---|
5 | Нет детей | 14091 |
0 | 1 ребенок | 4808 |
1 | 2 ребенка | 2052 |
2 | 3 ребенка | 330 |
3 | 4 ребенка | 41 |
4 | 5 детей | 9 |
# Функция которая нам подсчитает в процентах зависимость между количеством детей и возвратом кредита в срок
def children_analysis(data, index):
# построим сводную таблицу
data_children_pivot = data.pivot_table(index=index, \
values='debt', aggfunc=['sum', 'count', 'mean']).reset_index()
# Переименуем столбцы, чтобы не запутаться.
data_children_pivot = data_children_pivot.set_axis([index, 'sum_debt', 'count_family', 'correlation'], axis='columns')
# Возвращаем и сортируем по возрастанию
return data_children_pivot.sort_values('correlation')
☑️ Для начала необходимо понять, какие значения мы будем брать за основу, для проведения качественного анализа. Предлагаю заострить наше внимение на колонке count_family
и не брать во внимание значения ниже 1000, дабы не искажать реальную зависимость.
# Добавляем немного стиля и красок и смотрим, что получилось
display(children_analysis(data, 'children_category') \
.style.format({'correlation': '{:.2%}'}) \
.background_gradient(cmap='Reds',subset='correlation'))
children_category | sum_debt | count_family | correlation | |
---|---|---|---|---|
4 | 5 детей | 0 | 9 | 0.00% |
5 | Нет детей | 1063 | 14091 | 7.54% |
2 | 3 ребенка | 27 | 330 | 8.18% |
0 | 1 ребенок | 444 | 4808 | 9.23% |
1 | 2 ребенка | 194 | 2052 | 9.45% |
3 | 4 ребенка | 4 | 41 | 9.76% |
fig = px.line(children_analysis(data, 'children_category'), x='correlation', y='children_category', title='График зависимости между количеством детей и возвратом кредита в срок')
fig.show()
Вывод:
Однозначно, на первый взгляд прогладывается прямая зависимость между количеством детей и возвратом кредита в срок. Так с увеличением количества детей мы видим увеличение количества просроченных задолженностей, хотя люди с 3 детьми чаще платят в срок чем люди с 1 ребенком. Данные не однозначные, возможно, нужна большая выборка, чем та, которую мы имеем. Бездетные, как правило реже просрачивают оплату по кредиту, чем люди с детьми.
display(children_analysis(data, 'family_status') \
.style.format({'correlation': '{:.2%}'}) \
.background_gradient(cmap='Reds',subset='correlation'))
family_status | sum_debt | count_family | correlation | |
---|---|---|---|---|
2 | вдовец / вдова | 63 | 951 | 6.62% |
1 | в разводе | 84 | 1189 | 7.06% |
4 | женат / замужем | 927 | 12261 | 7.56% |
3 | гражданский брак | 385 | 4134 | 9.31% |
0 | Не женат / не замужем | 273 | 2796 | 9.76% |
plt.figure(figsize=(6,6))
ax = sns.barplot(data = children_analysis(data, 'family_status'), x ='family_status', y = 'correlation')
ax.set_xticklabels(children_analysis(data, 'family_status').family_status, rotation=45)
sns.despine()
Вывод:
В данном вопросе, вывод более менее очевиден. Чем серьезнее отношение - тем серьезные взгляды на жизнь! Если говорить про вдовцов (*я не берусь рассуждать о возрасте овдовевших людей*) и людей находящиеся в разводе, то когнитивное мышление данного контингента людей надодится после эмоциональной встряски - и следовательно - они к обязательствам относятся менее халатнее нежели людей нахоядщийся вне официального брака, то бишь, кредит берется более осознанно - возможно, данная зависимость косвенная, но все возможно
Разделим заемщиков на когорты в зависимости от величины дохода:
'E'
;'D'
;'C'
;'B'
;'A'
.display(children_analysis(data, 'total_income_category').sort_values(by='count_family', ascending=False) \
.style.format({'correlation': '{:.2%}'}) \
.background_gradient(cmap='Reds',subset='correlation'))
total_income_category | sum_debt | count_family | correlation | |
---|---|---|---|---|
2 | от 100 до 150 тыс. ₽ | 619 | 7110 | 8.71% |
3 | от 150 до 200 тыс. ₽ | 403 | 4738 | 8.51% |
10 | от 50 до 100 тыс. ₽ | 331 | 4073 | 8.13% |
4 | от 200 до 250 тыс. ₽ | 162 | 2242 | 7.23% |
5 | от 250 до 300 тыс. ₽ | 88 | 1323 | 6.65% |
6 | от 300 до 350 тыс. ₽ | 51 | 617 | 8.27% |
1 | Зарплата до 50 тыс.₽ | 23 | 371 | 6.20% |
7 | от 350 до 400 тыс. ₽ | 24 | 329 | 7.29% |
0 | Больше 500 тыс. ₽ | 14 | 222 | 6.31% |
8 | от 400 до 450 тыс. ₽ | 13 | 195 | 6.67% |
9 | от 450 до 500 тыс. ₽ | 4 | 111 | 3.60% |
Вывод
Если придерживаться этого правила, то нам, для качественного анализа подходят группы со следующим уровнем дохода:
В целом мы видим, что меньше всего просрочка у самых обеспеченных людей, однозначно, существует корреляция между уровнем дохода и возвратом кредита в срок. В красной зоне ребята с условно средним доходом.
display(children_analysis(data, 'purpose_category') \
.style.format({'correlation': '{:.2%}'}) \
.background_gradient(cmap='Reds',subset='correlation'))
purpose_category | sum_debt | count_family | correlation | |
---|---|---|---|---|
1 | операции с недвижимостью | 780 | 10751 | 7.26% |
3 | проведение свадьбы | 183 | 2313 | 7.91% |
2 | получение образования | 369 | 3988 | 9.25% |
0 | операции с автомобилем | 400 | 4279 | 9.35% |
Вывод:
В это таблице, все стоит на своих местах - операции с недвижимость в большинстве из случаев связаны с ипотечным кредитом - следовательно тут ряд гос. льгот в виде материнского капитала и прочих льгот.
Не знал, что на свадьбу берут кредиты - удивительно - но тут также все ясно
Автомобиль и образование в красной зоне - и тут ясно почему, я думаю, что все наслышаны всеми мутными историями и серыми схемами...
Ответ:
Думаю, что тут две причины: человеческий и технический фактор
Ответ:
Медиана - это робастная характеристика, которая игнорирует или же сглаживает случайные или не случайные выбросы и катаклизмы в целом. И если данные имеют нормальное распределение и их количество велико, то медиана будет равняться среднему значению.
Общий вывод
В ходе исследования надёжности заемщиков были изучены полученные данные, обработаны и заполнены пропуски, удалены дубликаты и клиенты были объединены в группы по разным параметрам. По итогам проделанной работы можно сделать следующие выводы:
В целом, бездетные семья реже допускают задержки в платежах по кредиту, % должников держится на уровне 7.54%, но данные не однозначные, возможно, нужна большая выборка, чем та, которую мы имеем. У группы клиентов, которые находятся или когда-либо находились в браке % должников на уровне 7% - это ниже на 2.1% ниже, чем у людей никогда не состоявших в официальном браке. Уровень дохода напрямую коррелирует с возвратом кредита в срок - в зоне риска по просрочке платежа люди со средним заработком.
Чаще всего возвращают в срок кредиты на собственное жилье, а больше всего задержек по кредитам на автомобили и образование. У групп клиентов, целью кредита у которых является автомобиль и образование: 9.3% должников (на 1.2% выше среднего).
Резюмируя можно сказать, что разброс значений по просроченным платежам не превышает двух процентов (от 7 до 9%), что означает разницу в вероятности просрочки платежа на 20%.