In [ ]:
%matplotlib inline
import pandas as pd
import seaborn as sns
from datetime import datetime

Вводные

Водитель может находиться в 4 возможных статусах:

* free -- доступен для нового заказа
* enroute -- едет на заказ
* ontrip -- выполняет заказ
* busy -- недоступен для нового заказа

Возможные переходы из одного состояние в другое определены как:

* free -> [free, enroute, busy]
* enroute -> [free, ontrip]
* ontrip -> [free]
* busy -> [free]

Почему переходы определяются таким образом:

  1. Из состояния free можно перейти в
    • free -- если водитель ушел в офлайн и заново вышел на линию, тогда подряд будет две записи со статусом free
    • enroute -- если водитель принял заказ, то переходит в статус enroute и едет к клиенту
    • busy -- если водитель нажал кнопку "Занят" в таксометре (пошел на обед и т.д.)
  2. Из состояния enroute можно перейти в
    • free -- если клиент или водитель отменил заказ
    • ontrip -- если водитель приехал к клиенту и начал выполнять заказ
  3. Из состояния ontrip можно перейти только в free (после выполнения заказа)
  4. Из состояния busy можно перейти только в free

Эффективность на поездке -- это время с клиентом в машине (ontrip), деленное на сумму длительностей всех статусов, связанных с поездкой (sum(free) + enroute + ontrip), где sum(free) -- время простоя.

Время простоя -- это сумма всех статусов free, предшествующих поездке. Суммируются все статусы free, идущие подряд, а также те, которые были прерваны короткими статусами busy или enroute (короткий статус == меньше какого-то TIMEOUT'а).

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

* driver_id -- id водителя
* status -- один из статусов
* dttm -- время начала статуса

Примечания:

* Поездка считается только при наличии статуса ontrip
* Тесты написаны для python 2
1. Написать функцию-генератор, которая будет отдавать соседние элементы в цикле. Функция понадобится для итерирования по записям водителя и проверки соседних статусов по условиям. Не забудьте проверить, что тесты проходят без ошибок (см. test_neighbors).

In [ ]:
def neighbors(iterable):
    # Write generator function which yields 
    # previous, current and next values in iterable list.
    # ... type your code here ...

In [ ]:
# Check if test passes
def test_neighbors():
    test_neighbors = neighbors( range(2) )
    assert test_neighbors.next() == (None, 0, 1)

test_neighbors()
2. Сгруппировать данные на уровне водителя таким образом, чтобы в одной строке находились все его записи со статусами и началом статуса списком:

Формат исходной таблицы:

driver_idstatusdttm
9f8f9bf3ee8f4874873288c246bd2d05free2018-02-04 00:19
9f8f9bf3ee8f4874873288c246bd2d05busy2018-02-04 01:03
8f174ffd446c456eaf3cca0915d0368dfree2018-02-03 15:43
8f174ffd446c456eaf3cca0915d0368denroute2018-02-03 17:02
.........

Формат сгруппированной таблицы:

driver_iddriver_info
9f8f9bf3ee8f4874873288c246bd2d05[("free", 2018-02-04 00:19), ("busy", 2018-02-04 01:03)]
8f174ffd446c456eaf3cca0915d0368d[("free", 2018-02-03 15:43), ("enroute", 2018-02-03 17:02) ...]

In [ ]:
df = pd.read_csv(".../dataset.csv", parse_dates=["dttm"])
# ... type your code here ...
3. Используя функцию neighbors, написать функцию, которая для каждой записи в списке driver_info посчитает ее длительность.

In [ ]:
def calc_status_duration(driver_info):
    driver_info_updated = []
    for i, j, k in neighbors(driver_info):
        # ... type your code here ...
    return driver_info_updated

In [ ]:
# Check if test passes
def test_calc_status_duration():
    sample_driver_info = [("free", datetime(2018, 4, 2, 0, 19)), 
                          ("busy", datetime(2018, 4, 2, 1, 3)),]
    sample_driver_info_updated = [('free', datetime(2018, 4, 2, 0, 19), 2640.0),
                                  ('busy', datetime(2018, 4, 2, 1, 3), None),]
    assert calc_status_duration(sample_driver_info) == sample_driver_info_updated

test_calc_status_duration()

In [ ]:
df["driver_info"] = df.driver_info.apply(calc_status_duration)
4. Используя функцию neighbors, написать функцию, которая сформирует из списка driver_info список поездок с информацией о длительности поездки и эффективности (duration_ontrip, efficiency).

In [ ]:
TIMEOUT = 1600

def collapse_statuses(driver_info):
    # Here define conditions under which the "free" state 
    # should be attributed to the trip.
    # ... type your code here ...

In [ ]:
# Check if test passes
def test_collapse_statuses():
    sample_driver_info = [("free", datetime(2018, 4, 2, 0, 19), 2640.0), 
                          ("busy", datetime(2018, 4, 2, 1, 3), 1660.0),
                          ("free", datetime(2018, 4, 2, 1, 30, 40), 2050.0),
                          ("enroute", datetime(2018, 4, 2, 2, 4, 50), 70.0),
                          ("free", datetime(2018, 4, 2, 2, 6), 500.0),
                          ("enroute", datetime(2018, 4, 2, 2, 14, 20), 520.0),
                          ("ontrip", datetime(2018, 4, 2, 2, 23), 3060.0),
                          ("free", datetime(2018, 4, 2, 3, 14), None)
                         ]
    sample_driver_info_updated = [(3060.0, 3060.0 / (3060.0 + 520.0 + 500.0 + 2050.0))]
    assert collapse_statuses(sample_driver_info) == sample_driver_info_updated

test_collapse_statuses()

In [ ]:
df["driver_info"] = df.driver_info.apply(collapse_statuses)
5. Нарисовать и проинтерпретировать зависимость между длительностью поездки и эффективностью.
Подсказка: требуется сделать обратное преобразование из таблицы со строками на уровне водителя в таблицу со строками на уровне поездки.

In [ ]: