Як програміст машину купував

Нещодавно я перейнявся пошуком б.у. автомобіля, натомість тільки що проданого, і, як це зазвичай буває, на цю роль претендували кілька конкурентів.

Як відомо, для покупки авто на території РФ існує кілька великих авторитетних сайтів (auto.ru, drom.ru, avito.ru), пошуку на яких я і віддав перевагу. Моїм вимогам відповідали сотні, а для деяких моделей і тисячі автомобілів, з перерахованих вище сайтів. Крім того, що шукати на кількох ресурсах незручно, так ще, перш ніж їхати дивитися авто «наживо», я хотів би відібрати вигідні (ціна яких щодо ринку занижена) пропозиції щодо апріорної інформації яку надає кожен з ресурсів. Я, звичайно, дуже хотів вирішити кілька перевизначених систем алгебраїчних рівнянь (можливо і нелінійних) високої розмірності вручну, але пересилив себе, і вирішив цей процес автоматизувати.

Збір даних
Дані я збирав з усіх описаних вище ресурсів, а мене цікавили наступні параметри:

  • ціна (price)
  • рік випуску (year)
  • пробіг (mileage)
  • об'єм двигуна (engine.capacity)
  • потужність двигуна (engine.power)
  • тип двигуна (2 індикаторні взаємовиключні змінні diesel і hybrid, що приймають значення 0 або 1, для дизельних і гібридних двигунів відповідно). Тип двигуна за замовчуванням — бензиновий (не винесено в третю змінну під уникнення мультиколлинеарности).

    Таким чином:

    image

    Далі подібна логіка для індикаторних змінних мається на увазі за умовчанням.
  • тип кпп (індикаторна мінлива mt (manual transmission), приймаюча логічне значення, для механічної коробки передач). Тип кпп за замовчуванням — автоматична.

    Необхідно відзначити, що до автоматичних коробок передач я відносив не тільки класичний гідравлічний автомат, але також роботизовану механіку і варіатор.
  • тип приводу (2 індикаторні змінні front.drive і rear.drive приймають булеві значення). Тип приводу за замовчуванням — повний.
  • тип кузова (7 індикаторних змінних sedan, hatchback, wagon, coupe, cabriolet, мінівен, pickup, що приймають булеві значення). Тип кузова за замовчуванням — позашляховик/кроссовер
Незаповнені ледачими продавцями дані я відзначав як NA (Not Available), щоб можна було коректно обробляти ці значення за допомогою R.

Візуалізація отриманих даних
Щоб не вдаватися в суху теорію, давайте розглянемо конкретний приклад, будемо шукати вигідні Mercedes-Benz E-klasse не старше 2010 року випуску, вартістю до 1.5 млн. рублів в Москві. Для того, щоб почати працювати з даними, першим ділом заповнюємо пропущені значення (NA) на медіанні, благо для цього в R є функція median().

dat <- read.csv("dataset.csv") # завантажуємо вибірку в R

dat$mileage[is.na(dat$mileage)] <- median(na.omit(dat$mileage)) # наприклад для пробігу

Для решти змінних процедура ідентична, тому опущу цей момент.

Тепер подивимося як ціна залежить від регрессоров (візуалізація індикаторних змінних на даному етапі нас не цікавить).

image

Виявляється за ці гроші є кілька машин 2013 року і навіть одна 2014!

image

Очевидно, що чим менше пробіг, тим ціна вище.

image

image

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

Хочу звернути вашу увагу на той факт, що в більшості статей з машинного навчання, які траплялися мені останнім часом, в тому числі на «Хабре», дуже мало уваги приділяється обґрунтуванню правомірності використання обраної моделі, її діагностики і помилки.

Тому, щоб оцінки отримані нами були заможними, розумно розглянути питання про коректність обраної нами моделі.

Діагностика моделі
В попередньому розділі, на основі експериментальних даних було зроблено припущення про лінійність розглянутої моделі щодо ціни, тому в цьому розділі мова піде про множинної лінійної регресії, її діагностиці і помилки.

Щоб модель була коректною, необхідно виконання умов теорема Гаусса-Маркова:

  1. Модель даних правильно специфікована, тобто:
    • відсутні пропущені значення.

      ДокладнішеЦе умова виконано (див. розділ Візуалізація отриманих даних), пропущені значення замінені на медіанні.

    • відсутня мультиколінеарності між регрессорами.

      ДокладнішеПеревіримо, чи виконується ця умова:

      dat_cor <- as.matrix(cor(dat)) # розрахунок кореляції між змінними
      
      dat_cor[is.na(dat_cor)] <- 0 # замінюємо пропущені значення на 0 (т. к. наприклад в кузові пікап або мінівен шуканого авто не буває)
      
      library(corrplot) # підключаємо бібліотеку corrplot, для гарної візуалізації
      
      palette <-colorRampPalette(c("#7F0000","red","#FF7F00","yellow","#7FFF7F", "cyan", "#007FFF", "blue","#00007F"))
      
      corrplot(dat_cor, method="color", col=palette(20), cl.length=21,order = "AOE", addCoef.col="green") # малюємо таблицю залежностей між змінними
      

      image

      Видно що присутня велика кореляція (>0.7) між об'ємом і потужністю двигуна, тому при подальшому аналізі ми не будемо враховувати мінливу engine.capacity, т. до. саме потужність двигуна дозволить більш точно побудувати регресійну модель порівняно з обсягом двигуна (атмосферний бензиновий, бензиновий з турбонаддувом, дизельний мотори — при одному і тому ж обсязі можуть мають різну потужність).

    • відсутні выбросы.

      ДокладнішеВикиди — показники виділяються із загальної вибірки (див. розділ Візуалізація отриманих даних), надають істотне вплив на оцінки коефіцієнтів регресійної моделі. Статистичний метод, здатний діяти в умовах викидів, називається робастным — лінійна регресія до них не відноситься, на відміну, наприклад, від робастной регресії Х'юбера або методу урізаних квадратів.

      Заходи впливу викидів на оцінки моделі можна підрозділити на загальні та специфічні. Загальні заходи, такі як відстань Кука, dffits, ковариацинное ставлення (covratio) відстань Махаланобіса, показують як i-те спостереження впливає на стан всієї регресійної залежності, їх ми і будемо використовувати для ідентифікації викидів. Специфічні заходи впливу, такі як dfbetas, показують вплив i-того спостереження на окремі параметри регресійної моделі.

      Ковариацинное ставлення (covratio) — загальна міра впливу спостереження. Являє собою відношення детермінанта ковариационной матриці з віддаленим i-им наглядом до детерминанту ковариацинной матриці для всього набору даних.

      Відстань Кука представляє собою квадратичну залежність від внутрішнього стьюдентизированного залишку, який не рекомендується використовувати для виявлення викидів, на відміну від зовнішнього стьюдентизированного залишку, від якого лінійно залежить dffits.

      Відстань Махаланобіса — міра видалення спостереження від центру системи, але оскільки вона не враховує залежний або незалежний характер змінної і розглядає їх в хмарі розсіювання рівнозначно, дана міра не призначена для регресійного аналізу.
      Таким чином, для виявлення викидів будемо використовувати заходи dffits і covratio.

      model <- lm(price ~ year + mileage + diesel + hybrid + mt + front.drive + rear.drive + engine.power + sedan + hatchback + wagon + coupe + cabriolet + мінівен + pickup, data = dat) # лінійна модель
      
      model.dffits <- dffits(model) # розрахуємо міру dffits
      

      Значущими для заходів dffits є показники перевищують величину 2*sqrt(k/n) = 0.42, тому слід відкинути (k — кількість змінних, n — число рядків вибірки).

      model.dffits.we <- model.dffits[model.dffits < 0.42]
      
      model.covratio <- covratio(model) # розрахуємо ковариационное відносини для моделі
      

      Значущі заходи для covratio показники можна знайти з нерівності | model.covratio[i] -1 | > (3*k)/n.

      model.covratio.we <- model.covratio[abs(model.covratio -1) < 0.13]
      
      dat.we <- dat[intersect(c(rownames(as.matrix(model.dffits.we))), c(rownames(as.matrix(model.covratio.we)))),] # спостереження без викидів
      
      model.we <- lm(price ~ year + mileage + diesel + hybrid + mt + front.drive + rear.drive + engine.power + sedan + hatchback + wagon + coupe + cabriolet + мінівен + pickup, data = dat.we) # лінійна модель побудована за вибіркою без викидів
      

      А тепер, після видалення спостережень виділяються із загальної вибірки, подивимося на графіки залежностей ціни від регрессоров.

      image

      image

      image

      Отже, обраний нами шлях ідентифікації викидів дозволив виключити із загального вибірки 18 спостережень, що безсумнівно позитивно позначиться на точності визначення коефіцієнтів лінійної моделі за допомогою МНК.

  2. Всі регрессоры детерміновані і не рівні.

    ДокладнішеДана умова виконано.

  3. Помилки не носять систематичного характеру, дисперсія помилок однакова (гомоскедастичность)

    ДокладнішеПодивимося як розподілені помилки моделі (для розрахунку помилок моделі в R є функція resid()).

    plot(dat.we$year, resid(model.we))
    plot(dat.we$mileage, resid(model.we))
    plot(dat.we$engine.power, resid(model.we))
    

    image

    image

    image

    Помилки більш-менш рівномірно розподілені відносно горизонтальної осі, що дозволяє не сумніватися у виконання умови гомоскедастічності.

  4. Помилки розподілені нормально.

    ДокладнішеДля перевірки даної умови побудуємо графік квантилів залишків проти квантилів, які можна було б очікувати за умови, що залишки нормально розподілені.

    qqnorm(resid(model.we)) 
    qqline(resid(model.we))
    

    image
Випробовування обраної моделі
Отже, настав момент, коли з бюрократією покінчено, коректність використання моделі лінійної регресії не викликає сумнівів. Нам залишається тільки порахувати коефіцієнти лінійного рівняння, за яким ми зможемо отримати свої, передбачені ціни на автомобілі і порівняти їх з цінами оголошень.

model.we <- lm(price ~ year + mileage + diesel + hybrid + mt + front.drive + rear.drive + engine.power + sedan + hatchback + wagon + coupe + cabriolet + мінівен + pickup, data = dat.we)

coef(model.we) # коефіцієнти лінійної моделі
(Intercept) year mileage diesel rear.drive engine.power sedan 
-1.76 e+08 8.79 e+04 -1.4 e+00 2.5 e+04 4.14 e+04 2.11 e+03 -2.866407 e+04 

predicted.price <- predict(model.we, dat) # предскажем ціну за отриманими коефіцієнтами

real.price <- dat$price # вектор цін на автомобілі отриманий з оголошень

profit <- predicted.price - real.price # вигода між передбаченої нами ціною і ціною оголошень

А тепер, давайте зберемо всі виконані дослідження в купу, заради одного самого інформативного графіка, який покаже нам саму бажану для будь-якого автомобільного перекупника величину, величину вигоди від покупки конкретного авто відносно ринкової ціни.

plot(real.price,profit)
abline(0,0)
</code>
<img src="https://habrastorage.org/files/a76/d89/7d6/a76d897d644e45268d9c49ffdd572f77.jpeg" alt="image"/>

І на яку вигоду у відсотковому співвідношенні можна розраховувати?

<source>
sorted <- sort(predicted.price /real.price, decreasing = TRUE)
sorted[1:10]
69 42 122 248 168 15 244 271 109 219 
1.590489 1.507614 1.386353 1.279716 1.279380 1.248001 1.227829 1.209341 1.209232 1.204062 

Так, економія 59% — це дуже здорово, але вельми сумнівно, потрібно дивитися авто «наживо», т. к. безкоштовний сир зазвичай в мишоловці, ну або продавцю терміново потрібні гроші. А от починаючи з 4-го місця (економія 28%) і далі, результат видається цілком реалістичним.

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

Наостанок
Дорогі друзі, я сам, прочитавши таку статтю поставив би в першу чергу 3 питання:

  1. Яка у моделі точність?
  2. Чому обрана проста лінійна регресія і де порівняння з іншими моделями?
  3. А чому б не зробити сервіс для пошуку вигідних автомобілів?
Тому відповідаю:

  1. Протестуємо модель за схемою 80/20.

    split <- runif(dim(dat.we)[1]) > 0.2 # розділяємо нашу вибірку
    
    train <- dat.we[split,] # вибірка для навчання
    
    test <- dat.we[!split,] # тестова вибірка
    
    train.model <- lm(price ~ year + mileage + diesel + hybrid + mt + front.drive + rear.drive + engine.power + sedan + hatchback + wagon + coupe + cabriolet + мінівен + pickup, data = train) # лінійна модель побудована за вибіркою для навчання
    
    predictions <- predict(train.model, test) # перевіримо точність на тестовій вибірці
    
    print(sqrt(sum((as.vector(predictions - test$price))^2))/length(predictions)) # точність передбачення ціни (в рублях)
    [1] 11866.34 
    

    Багато це чи мало — можна дізнатися тільки в порівнянні.
  2. Лінійна регресія, як один з азів машинного навчання дозволяє не відволікатися на вивчення алгоритму, а повністю зануритися в саму задачу.

    Тому я вирішив розділити свою оповідь на 2 (а там як піде) статті.

    В цій статті мова піде про порівняння моделей для рішення нашої задачі з метою виявлення переможця.
  3. З даної тематики був розроблений сервіс, в основі якого лежить модель-переможець, про яку піде мова в наступній статті.

    І ось, що з цього вийшло — Mercedes-Benz E-klasse не старше 2010 року випуску, вартістю до 1.5 млн. рублів в Москві.

    image

    image

Посилання
1. Вихідні дані у форматі csv — dataset.txt
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.