Cars' data analysis

In this dataset I have information about 10 cars driving on October 8, 2016. I'm going to get as many insights as possible from this data and generate ideas about the ways to sell this data and get profit.

A very important point first. October 8, 2016 is Saturday, which is a weekend in United Arab Emirates (cars coordinates are in this country), so this influences the people activity: some could work, some could entertain themselves, some could buy products for the week and so on.

Also some additional information: As far as I know speed limit in United Arab Emirates is up to 120 km/h on main roads and 140 km/h on E11. Many drivers maintain higher speed between the speed cameras. So drivers with speeds exceeding the limits should be considered risky.

My analysis goes through the following steps:

  • data preparation (dropping unnecessary data and duplicate rows as well as transforming variables);
  • looking at basic general information about the cars;
  • main analysis of the data and drawing driving pathes of the cars;
  • conclusions;
In [1]:
#Libraries
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
import folium
from folium import plugins
import datetime
from datetime import datetime
In [2]:
#Reading the data.
data = pd.read_excel('/devices_data.xlsx', encoding='utf-8')
In [3]:
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 64094 entries, 0 to 64093
Data columns (total 21 columns):
DeviceNumber       64094 non-null object
TimeUtc            64094 non-null datetime64[ns]
Version            64094 non-null int64
Mileage            64094 non-null int64
Longitude          64094 non-null float64
Latitude           64094 non-null float64
Speed              64094 non-null int64
EngineRPM          64094 non-null int64
LockTrunk          64094 non-null int64
LockHood           64094 non-null int64
LockDriver         64094 non-null int64
LockTPassenger     64094 non-null int64
LockLeftRear       64094 non-null int64
LockRightRear      64094 non-null object
LastSync           64094 non-null datetime64[ns]
LastNumber         64094 non-null int64
EngineOn           64094 non-null int64
Temperature        64094 non-null int64
FuelLevel          64094 non-null int64
AlarmOn            64094 non-null int64
SatellitesCount    64094 non-null int64
dtypes: datetime64[ns](2), float64(2), int64(15), object(2)
memory usage: 10.3+ MB

No missing values, which is convenient. Two columns have data in datetime format, other columns (except DeviceNumber) are numerical. Also LockRightRear has type 'object', which is strange, I'll look into it.

In [4]:
data.head()
Out[4]:
DeviceNumber TimeUtc Version Mileage Longitude Latitude Speed EngineRPM LockTrunk LockHood ... LockTPassenger LockLeftRear LockRightRear LastSync LastNumber EngineOn Temperature FuelLevel AlarmOn SatellitesCount
0 device_1 2016-10-08 00:00:00 87 639000 53.726430 23.126805 0 0 0 0 ... 0 0 00:00:00 2016-10-08 00:00:00 13 0 38 0 0 9
1 device_2 2016-10-08 00:00:06 87 55341000 56.188661 25.518500 0 0 0 0 ... 0 0 00:00:00 2016-10-08 00:00:06 22 0 42 0 0 9
2 device_1 2016-10-08 00:00:09 87 639000 53.726430 23.126805 0 0 0 0 ... 0 0 00:00:00 2016-10-08 00:00:09 13 0 38 0 0 9
3 device_3 2016-10-08 00:00:18 87 227000 55.244163 25.205136 0 0 0 0 ... 0 0 00:00:00 2016-10-08 00:00:18 21 0 43 0 0 4
4 device_4 2016-10-08 00:00:22 87 354000 55.399996 25.278216 0 0 0 0 ... 0 0 00:00:00 2016-10-08 00:00:22 14 0 45 0 0 9

5 rows × 21 columns

This is how the data looks like. Let's start the analysis.

In [5]:
len(data.FuelLevel.unique()), len(data.AlarmOn.unique())
Out[5]:
(1, 1)

FuelLevel and AlarmOn have only one distinct value each, this means that fuel level isn't measured. Alarm is also not measured or isn't activated. So these columns can be dropped.

In [6]:
data.LockRightRear.value_counts()
Out[6]:
00:00:00               63937
1900-01-01 00:00:00      157
Name: LockRightRear, dtype: int64

LockRightRear column has two distinct values and they are strange. I suppose that there was a problem with formatting, so '00:00:00' is 0 and the other value is 1. I'll fix it.

In [7]:
data['LockRightRear'] = data['LockRightRear'].apply(lambda x: 0 if str(x) == '00:00:00' else 1)
In [8]:
data.loc[data.Version == 87].DeviceNumber.unique(), data.loc[data.Version == 94].DeviceNumber.unique()
Out[8]:
(array(['device_1', 'device_2', 'device_3', 'device_4', 'device_5',
        'device_8', 'device_9', 'device_10'], dtype=object),
 array(['device_6', 'device_7'], dtype=object))

Two cars have one version, the rest have another. I suppose that this is software version. Not much useful information there, so I'll drop it.

In [9]:
data.loc[data.TimeUtc != data.LastSync]
Out[9]:
DeviceNumber TimeUtc Version Mileage Longitude Latitude Speed EngineRPM LockTrunk LockHood ... LockTPassenger LockLeftRear LockRightRear LastSync LastNumber EngineOn Temperature FuelLevel AlarmOn SatellitesCount
11265 device_8 2016-10-08 08:00:38 87 11000 55.477608 25.349330 0 803 0 0 ... 0 0 0 2016-10-08 08:00:35 14 1 43 0 0 7
11280 device_8 2016-10-08 08:00:50 87 11000 55.477608 25.349330 0 0 0 0 ... 0 0 0 2016-10-08 08:00:47 15 0 43 0 0 7
11302 device_8 2016-10-08 08:01:18 87 11000 55.477608 25.349330 0 800 0 0 ... 0 0 0 2016-10-08 08:01:15 16 1 43 0 0 7
16981 device_8 2016-10-08 10:12:34 87 19000 55.441016 25.341671 0 712 0 0 ... 0 0 0 2016-10-08 10:11:40 17 1 47 0 0 10
17012 device_8 2016-10-08 10:13:02 87 19000 55.441016 25.341671 0 0 0 0 ... 0 0 0 2016-10-08 10:12:47 18 0 47 0 0 10

5 rows × 21 columns

There is a column with LastSync time, but it differs from TimeUtc only in 5 rows and the difference isn't high. So this column can also be dropped.

In [10]:
for i in range(1, 11):
    dev = 'device_' + str(i)
    print(dev, data.loc[data.DeviceNumber == str(dev)].LastNumber.unique())
device_1 [13]
device_2 [22]
device_3 [21]
device_4 [14]
device_5 [16]
device_6 [17]
device_7 [25]
device_8 [13 14 15 16 17 18]
device_9 [18]
device_10 [27]

Nine devices have one value of LastNumber and the 8th device has 6 distinct values. I have no idea about what 'LastNumber' is, so I'll drop it.

In [11]:
#Drop columns
data.drop(['FuelLevel', 'AlarmOn', 'LastSync', 'Version', 'LastNumber'], axis=1, inplace=True)
In [12]:
len(data.drop_duplicates(subset=['DeviceNumber', 'TimeUtc'], keep=False)) / len(data)
Out[12]:
0.7785596155646395

Almost 23% of all rows are duplicates, this means there are many lines with the same time for a device. I decided to completely drop these columns: data granularity is high, so this won't have a serious impact. And if some data looks strange after dropping the rows - I can get them back.

In [13]:
data.drop_duplicates(subset=['DeviceNumber', 'TimeUtc'], keep=False, inplace=True)
In [14]:
data.loc[data.DeviceNumber == 'device_1']['Mileage'].max() - data.loc[data.DeviceNumber == 'device_1']['Mileage'].min()
Out[14]:
314000
In [15]:
data.Speed.max()
Out[15]:
208

So max speed is 208, I think this is km/hour (if this is miles/hour, then the speed is ~344 km/hour, which is too high for normal car). And day distance for the first car is 314000. I think these are meters, so I'll convert them to kilometers.

In [16]:
data['Mileage'] = data['Mileage'] / 1000
In [17]:
#All the data is in one day, so I'll leave just the time.
data['TimeUtc'] = data['TimeUtc'].dt.time
In [18]:
data.head()
Out[18]:
DeviceNumber TimeUtc Mileage Longitude Latitude Speed EngineRPM LockTrunk LockHood LockDriver LockTPassenger LockLeftRear LockRightRear EngineOn Temperature SatellitesCount
0 device_1 00:00:00 639.0 53.726430 23.126805 0 0 0 0 0 0 0 0 0 38 9
1 device_2 00:00:06 55341.0 56.188661 25.518500 0 0 0 0 0 0 0 0 0 42 9
2 device_1 00:00:09 639.0 53.726430 23.126805 0 0 0 0 0 0 0 0 0 38 9
3 device_3 00:00:18 227.0 55.244163 25.205136 0 0 0 0 0 0 0 0 0 43 4
4 device_4 00:00:22 354.0 55.399996 25.278216 0 0 0 0 0 0 0 0 0 45 9

This is how the data looks like after the changes.

At first I wasn't sure how to interpret information in columns LockTrunk, LockHood, LockDriver, LockTPassenger, LockLeftRear, LockRightRear. They have a lot of zero values and only several percent of values '1'. Then I found out that values '1' appeared in columns LockTrunk, LockHood and LockRightRear only when speed was zero. And '1' appeared in other columns at zero or low speeds. I colclude that '1' means that trunk, hood or a door was opened.

If the hood is opened, this could mean that there are some problems with the car or that it had a planned check-up.

If the trunk is opened, I assume that some baggage was put into it or was taken from it.

Opened driver door means that the driver enteres or leaves the car.

Opened passenger or rear door means that there was a passenger on the relevant seat. Well, it could also mean that something was put onto or taken from the relevant seat, but I have no way to distinguish. Also one should notice that if only one back door was opened, it doesn't mean that there is only one passenger on the back seat.

Now let's get some basic information.

In [19]:
for i in range (1,11):
    device = 'device_' + str(i)
    data_device = data.loc[data.DeviceNumber == str(device)]
    distance = data_device['Mileage'].max() - data_device['Mileage'].min()
    trunk = '' if data_device['LockTrunk'].max() == 0 else 'Opened trunk. '
    hood = '' if data_device['LockHood'].max() == 0 else 'Opened hood. '
    tpassenger = '' if data_device['LockTPassenger'].max() == 0 else 'Has tpassenger. '
    rear = '' if data_device['LockLeftRear'].max() == 0 or data_device['LockRightRear'].max() == 0 else 'Has back passenger.'
    print(device[7:], 'Mileage:', data_device['Mileage'].min(), '+', distance, 'or', 
        '{:.2f}%.'.format((distance / data_device['Mileage'].min()) * 100),
          trunk + hood + tpassenger + rear)
1 Mileage: 639.0 + 314.0 or 49.14%. Opened trunk. Has tpassenger. Has back passenger.
2 Mileage: 55341.0 + 345.0 or 0.62%. Has tpassenger. Has back passenger.
3 Mileage: 227.0 + 29.0 or 12.78%. Has tpassenger. 
4 Mileage: 354.0 + 44.0 or 12.43%. Has tpassenger. Has back passenger.
5 Mileage: 223.0 + 0.0 or 0.00%. Opened trunk. Opened hood. Has tpassenger. 
6 Mileage: 243.0 + 303.0 or 124.69%. Opened trunk. Has tpassenger. Has back passenger.
7 Mileage: 18.0 + 278.0 or 1544.44%. Opened trunk. Opened hood. Has tpassenger. Has back passenger.
8 Mileage: 11.0 + 437.0 or 3972.73%. Opened trunk. Opened hood. Has tpassenger. Has back passenger.
9 Mileage: 326.0 + 13.0 or 3.99%. Opened trunk. Has tpassenger. Has back passenger.
10 Mileage: 151.0 + 131.0 or 86.75%. Opened trunk. Has tpassenger. Has back passenger.

Seven cars used trunk, 3 cars opened hood, all had a passeanger at the front seat, 8 had rear passengers.

One car (5) didn't drive at all. Cars 7 and 8 are quite new - mileage is really low. Car 9 drove a little, cars 3 and 4 drove a bit more. Cars 2 and 8 drove quite far. And car 2 has the highest starting mileage.

This is a basic analysis and the conclusions may be changed.

In [20]:
data1 = data.loc[data.DeviceNumber == 'device_1']
data1['acceleration1'] = data1.Speed.shift(-1) - data1.Speed
data1.loc[data1.acceleration1 > 40].acceleration1
D:\Programs\Anaconda3\lib\site-packages\ipykernel\__main__.py:2: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  from ipykernel import kernelapp as app
Out[20]:
5999     66.0
6070     99.0
16905    46.0
17002    60.0
17007    68.0
17058    49.0
17167    58.0
21250    41.0
27622    46.0
28832    52.0
29116    65.0
45330    42.0
Name: acceleration1, dtype: float64

I wanted to analyse acceleration of cars to determine drivers who are prone to abrupt changes is speed, which could cause incidents. But there are a lot of peaks in the data - such high acceleration isn't physically possible. Cleaning or smoothing the data is complicated, so I'll just analyse the speeds.

In [21]:
#The function to plot a column for each device on a separate graph.
def plot_column(column):
    fig, axes = plt.subplots(nrows=5, ncols=2)
    #This sets axes based on device number.
    for i in range(1,11):
        dev = 'device_' + str(i)
        if i in (1,2):
            data.loc[data.DeviceNumber == str(dev)].plot(
                x='TimeUtc', y=column, figsize=(15, 24), ax=axes[0,i-1]); axes[0,i-1].set_title(str(dev));
        elif i in (3,4):
            data.loc[data.DeviceNumber == str(dev)].plot(
                x='TimeUtc', y=column, figsize=(15, 24), ax=axes[1,i-3]); axes[1,i-3].set_title(str(dev));
        elif i in (5,6):
            data.loc[data.DeviceNumber == str(dev)].plot(
                x='TimeUtc', y=column, figsize=(15, 24), ax=axes[2,i-5]); axes[2,i-5].set_title(str(dev));
        elif i in (7,8):
            data.loc[data.DeviceNumber == str(dev)].plot(
                x='TimeUtc', y=column, figsize=(15, 24), ax=axes[3,i-7]); axes[3,i-7].set_title(str(dev));
        elif i in (9,10):
            data.loc[data.DeviceNumber == str(dev)].plot(
                x='TimeUtc', y=column, figsize=(15, 24), ax=axes[4,i-9]); axes[4,i-9].set_title(str(dev));
In [22]:
plot_column('Temperature')

The temperature is roughly the same for all cars. In some cases it is lower than 30 or higher than 50. I suppose that it could reach high levels during long stops at sunny places.

In [23]:
plot_column('SatellitesCount')

This gives me little information. Maybe the number of satellites descreases during long stops, as less presicion is required. And more satellites are necessary when the car is driving.

Now I analyse the data for each car. I take the following steps:

  • Find when a car stops and starts/continues driving. At first I wanted to use column "EngineOn" to determine the "state" of the car, but often cars stayed at the same place for a long time with the engine turned on. So I decided to use Speed. If car's speed is greater than 0, it moves. But car could make short stops due to traffic lights or traffic jams or for some other reasons. So I think that car does a meaningfull stop if it stays motionless for more than 3 minutes;
  • Now I can get information about the time at which the car stops or moves;
  • I use folium module to plot car's driving path and to shows stops. Markers show the place and the time of the stops as well as the stop's length. Clicking on the map shows a popup with the coordinates;
  • Then I plot several graphs: speed and EngineRPM, speed and EngineRPM when the speed is greater than zero (this makes it easier to see the high values);
  • At last I show detailed text information about each part of the travel. For each segment I show start and end time. When a car drives, I show mileage and average speed, EngineRPM and temperature. For a motionless car I show whether Trunk, Hood, Driver door, Passenger door, Left rear door and Right rear door were opened.
  • Based on these information I analyse the cars.
  • The function below performs all the necessary steps, so I just need to call it and to specify the device number.
  • I can't analyse some of the stops, as there are no notable places near them. I usually omit these stops from analysis.
  • I don't know the gender of the driver, so I'll 'he/she' pronoun.
  • As the data covers only one day and the day is weekend, conclusions are made based only on this day. Usual behavious can't be derived, so data shows only how a driver spends one of his weekends. If we need to create a behavioral profile of the driver, data for more days is necessary.
In [24]:
def car_data(device):
    #Use the data only for the current device.
    data_device = data.loc[data.DeviceNumber == str(device)]
    #Starting coordinates, coordinates of all stops will be added to this list.
    loc = [(data_device.iloc[0,3], data_device.iloc[0,4])]
    #Time of stops.
    stop_time = [data_device.iloc[0,1]]
    #Length of stops.
    stop_length = []
    #Time of the last stop. Mainly to check that the length of the stop is greater than 3 minutes.
    last_stop = str(data_device.iloc[0,1])
    #Time of leaving the stops.
    move_time = []
    #I go through each row to determine whether the car stops or starts moving (speed becomes 0 or becomes > 0 respectively).
    #If the car moves for the first time, I write first moving time and the length of the initial stops.
    #Then if a car stops and the length of the stop is > 180 seconds, I add information about the coordinates and the time.
    for i in range(1, len(data_device)):    
        if data_device.iloc[i,5] > 0 and data_device.iloc[i-1,5] == 0:
            if last_stop == str(data_device.iloc[0,1]):
                stop_length.append(data_device.iloc[i,1])
                move_time.append(data_device.iloc[i,1])
            else:
                if (datetime.strptime(str(data_device.iloc[i,1]), "%H:%M:%S") \
                    - datetime.strptime(str(last_stop), "%H:%M:%S")).seconds < 180:
                    pass
                else:
                    stop_length.append(datetime.strptime(str(data_device.iloc[i,1]), "%H:%M:%S") \
                    - datetime.strptime(str(last_stop), "%H:%M:%S"))
                    loc.append(loc_temp)
                    stop_time.append(stop_time_temp)
                    move_time.append(data_device.iloc[i,1])
        if data_device.iloc[i,5] == 0 and data_device.iloc[i-1,5] > 0:
            last_stop = data_device.iloc[i,1]
            loc_temp = (data_device.iloc[i,3], data_device.iloc[i,4])
            stop_time_temp = data_device.iloc[i,1]
    
    #Adding information about the final stop.
    loc.append(loc_temp)
    stop_time.append(stop_time_temp)
    stop_length.append('the rest of the day')
    
    #Writing information about distinct latitudes and longitudes for plotting.
    lat_device = list(data_device['Latitude'])
    lon_device = list(data_device['Longitude'])
    lat = [lat_device[0]]
    lon = [lon_device[0]]
    for i in range(1, len(lat_device)):
        if lat_device[i] != lat_device[i-1] or lon_device[i] != lon_device[i-1]:
            lat.append(lat_device[i])
            lon.append(lon_device[i])    
    
    #Plotting the car path on the map with markers for stops. First line centers map on the mean coordinates.
    cars_map = folium.Map(location=[np.mean(lat), np.mean(lon)], zoom_start=10)
    marker_cluster = folium.MarkerCluster().add_to(cars_map)
    #Adding markers for each stop with information about the stop.
    for i in range(len(loc)):
        folium.Marker([loc[i][1], loc[i][0]], popup="Stopped at {0} for {1}".format(stop_time[i],
                                                                                    stop_length[i])).add_to(marker_cluster)
    #Plotting car path.
    folium.PolyLine(zip(lat, lon), color="blue", weight=2.5, opacity=1).add_to(cars_map)
    folium.LatLngPopup().add_to(cars_map)
    
    #Plotting graphs for various variables.
    fig, axes = plt.subplots(nrows=2, ncols=2)
    data_device.plot(x='TimeUtc', y='Speed', figsize=(18, 15), ax=axes[0,0]); axes[0,0].set_title('Speed');
    data_device.loc[data_device.Speed > 0].plot(
        x='TimeUtc', y='Speed', figsize=(18, 15), ax=axes[0,1]); axes[0,1].set_title('Non zero Speed');
    data_device.plot(x='TimeUtc', y='EngineRPM', figsize=(18, 15), ax=axes[1,0]); axes[1,0].set_title('EngineRPM');
    data_device.loc[data_device.Speed > 0].plot(
        x='TimeUtc', y='EngineRPM', figsize=(18, 15), ax=axes[1,1]); axes[1,1].set_title('EngineRPM at non zero Speed');
    
    #Showing car data.
    #Show mileage at the beginning of the day and driving distance for the day.
    mileage = int(data_device['Mileage'].min())
    distance = int(data_device['Mileage'].max()) - mileage
    print('Mileage at the beginning of the day:', str(mileage) + ' km.', 'Drove today', str(distance) + ' km',
          'or increased mileage by', '{:.2f}%.'.format((distance / mileage) * 100))

    #Information about the car at all parts of the travel. Two main states of the car are driving and staying.
    #What can be opened in a car during a stop.
    actions_list = ['LockTrunk', 'LockHood', 'LockDriver', 'LockTPassenger', 'LockLeftRear', 'LockRightRear']
    objects = ['Trunk', 'Hood', 'Driver door', 'Passenger door', 'Left rear door', 'Right rear door']
    #I need a list of timepoints. Didn't find a better way to do it.
    time_points = [None]*(len(stop_time) + len(move_time))
    time_points[::2] = stop_time
    time_points[1::2] = move_time

    #I go through timepoints. For each part of travel I get starting and ending time and the difference between them.
    #For stops I see whether something was opened in a car(column has at leat one 1). And average temperature.
    #For travels I get travel distance and average values of speed, EngineRPM and temperature.
    #For the final stop I get data separately as it differs from usual stops.
    for i in range(0, len(time_points), 2):
        #Actions - temporal list for i.
        actions = []
        if i < len(time_points) - 1:
            for j in range(len(actions_list)):
                if (data_device.loc[(data_device.TimeUtc > time_points[i])
                                    & (data_device.TimeUtc < time_points[i+1])][actions_list[j]] > 0).any():
                    actions.append(objects[j])
            #If nothing was opened, show "nothing"
            actions = actions if len(actions) > 0 else ['nothing']
            
            print(str(time_points[i]) + ' - ' + str(time_points[i+1]),
                  'Stayed for {0}.'.format((datetime.strptime(str(time_points[i+1]), "%H:%M:%S") \
                    - datetime.strptime(str(time_points[i]), "%H:%M:%S"))),
                 'Average temperature was {0}.'.format(round(data_device.loc[(data_device.TimeUtc > time_points[i])
                & (data_device.TimeUtc < time_points[i+1])]['Temperature'].mean())),
                 'Opened: {0}.'.format(', '.join(actions)))

            #Temporal data for the time interval.
            local_data = data_device.loc[(data_device.TimeUtc > time_points[i+1]) & (data_device.TimeUtc < time_points[i+2])]
            
            #There are cases when there is one strange line with nonzero speed among lines with zero speed. This is to check.
            if len(local_data) < 2:
                pass
            else:
                print(str(time_points[i+1]) + ' - ' + str(time_points[i+2]), 'Drove for {0}.'.format(
                        (datetime.strptime(str(time_points[i+2]), "%H:%M:%S") \
                        - datetime.strptime(str(time_points[i+1]), "%H:%M:%S"))),
                     'Traveled {0} km, with average speed {1}, EngineRPM {2} and temperature {3}.'.format(
                    local_data.Mileage.max()-local_data.Mileage.min(),
                        round(local_data.Speed.mean()),
                        round(local_data.EngineRPM.mean()), round(local_data.Temperature.mean())))
            
        elif i == len(time_points)-1:
            for j in range(len(actions_list)):
                if (data_device.loc[data_device.TimeUtc > time_points[i]][actions_list[j]] > 0).any():
                    actions.append(objects[j])
            actions = actions if len(actions) > 0 else ['nothing']
            print(str(time_points[i]) + ' - ' + 'midnight', 'Stayed till the end of the day.',
                 'Average temperature was {0}.'.format(
                    round(data_device.loc[data_device.TimeUtc > time_points[i]]['Temperature'].mean())),
                 'Opened: {0}.'.format(', '.join(actions)))
    
    return cars_map
In [25]:
car_data('device_1')
Mileage at the beginning of the day: 639 km. Drove today 314 km or increased mileage by 49.14%.
00:00:00 - 05:43:34 Stayed for 5:43:34. Average temperature was 36. Opened: Trunk, Driver door, Passenger door, Left rear door, Right rear door.
05:43:34 - 06:28:07 Drove for 0:44:33. Traveled 68.0 km, with average speed 103, EngineRPM 1934 and temperature 33.
06:28:07 - 07:34:43 Stayed for 1:06:36. Average temperature was 38. Opened: Trunk, Driver door, Passenger door, Left rear door.
07:34:43 - 07:38:52 Drove for 0:04:09. Traveled 1.0 km, with average speed 24, EngineRPM 1192 and temperature 46.
07:38:52 - 07:45:12 Stayed for 0:06:20. Average temperature was 44. Opened: Driver door.
07:45:12 - 07:48:42 Drove for 0:03:30. Traveled 2.0 km, with average speed 42, EngineRPM 1573 and temperature 41.
07:48:42 - 08:12:48 Stayed for 0:24:06. Average temperature was 37. Opened: Driver door.
08:12:48 - 08:21:20 Drove for 0:08:32. Traveled 7.0 km, with average speed 57, EngineRPM 1704 and temperature 33.
08:21:20 - 08:48:08 Stayed for 0:26:48. Average temperature was 37. Opened: Left rear door.
08:48:08 - 08:56:19 Drove for 0:08:11. Traveled 4.0 km, with average speed 32, EngineRPM 1309 and temperature 41.
08:56:19 - 10:06:16 Stayed for 1:09:57. Average temperature was 45. Opened: Driver door, Passenger door, Left rear door.
10:06:16 - 11:26:43 Drove for 1:20:27. Traveled 145.0 km, with average speed 120, EngineRPM 2235 and temperature 37.
11:26:43 - 13:22:51 Stayed for 1:56:08. Average temperature was 40. Opened: Trunk, Driver door, Passenger door, Left rear door, Right rear door.
13:22:51 - 14:01:58 Drove for 0:39:07. Traveled 39.0 km, with average speed 65, EngineRPM 1594 and temperature 36.
14:01:58 - 15:03:10 Stayed for 1:01:12. Average temperature was 36. Opened: Driver door.
15:03:10 - 15:15:00 Drove for 0:11:50. Traveled 4.0 km, with average speed 24, EngineRPM 1334 and temperature 40.
15:15:00 - 15:25:07 Stayed for 0:10:07. Average temperature was 37. Opened: nothing.
15:25:07 - 15:48:38 Drove for 0:23:31. Traveled 8.0 km, with average speed 38, EngineRPM 1360 and temperature 35.
15:48:38 - 16:27:05 Stayed for 0:38:27. Average temperature was 37. Opened: Driver door.
16:27:05 - 16:35:23 Drove for 0:08:18. Traveled 8.0 km, with average speed 66, EngineRPM 1466 and temperature 40.
16:35:23 - 16:41:36 Stayed for 0:06:13. Average temperature was 38. Opened: nothing.
16:41:36 - 17:00:42 Drove for 0:19:06. Traveled 27.0 km, with average speed 93, EngineRPM 1729 and temperature 36.
17:00:42 - midnight Stayed till the end of the day. Average temperature was 42. Opened: Driver door.
Out[25]:

So the car was at home at first. Then the family got into the car (all doors were opened) and some baggage was put into the trunk. They drove for 68 km to a place near a supermarket and KFC. I think that the whole family left the car and went to eat or to the supermarket. After this the driver returned to the car and for some time drove around the area. He/she drove to barber (or to a little cafe), then visited Starbucks. At last car went back to the supermarket to get the family and possible some more things from supermarket. I may guess that during the first stop the products were bought, and then the family went to purchase other items and this was too boring for the driver. Also it is possible that the driver prefers Starbucks, and his family - KFC.

Then they drove to a place in the city and the family left the car here. I suppose that this is their home in the city. After this the driver traveled alone. At first he/she stayed at National Galleria: there are shops, restaurants and other interesting places. Then he/she drove to some place, where he/she didn't even leave the car - maybe someone brought him something? After this he/she stopped near the ministry of foreign affairs (for some business maybe). At last he/she visited the gas station and went home after this at 17 hours.

Average speed was okay, but there were peaks of high speed occasionally and sometimes engime prm was too high. Also he/she drove a lot - almost half of the mileage. This could mean that the car is quite new or is rarely used.

So the driver is a family person. The family buys products on Sunday and eats breakfast in cafe. Then they go to their home in the city and stay their. The driver likes starbucks and deals with some matters in the ministry of foreign affairs. The speed is often risky and engine prm is sometimes pushed to high values.

In [26]:
car_data('device_2')
Mileage at the beginning of the day: 55341 km. Drove today 345 km or increased mileage by 0.62%.
00:00:06 - 06:57:06 Stayed for 6:57:00. Average temperature was 41. Opened: Driver door.
06:57:06 - 07:12:32 Drove for 0:15:26. Traveled 20.0 km, with average speed 87, EngineRPM 1874 and temperature 42.
07:12:32 - 07:16:01 Stayed for 0:03:29. Average temperature was 37. Opened: Driver door.
07:16:01 - 07:22:33 Drove for 0:06:32. Traveled 4.0 km, with average speed 39, EngineRPM 1440 and temperature 36.
07:22:33 - 08:25:38 Stayed for 1:03:05. Average temperature was 42. Opened: Driver door.
08:25:38 - 08:31:43 Drove for 0:06:05. Traveled 2.0 km, with average speed 31, EngineRPM 1562 and temperature 48.
08:31:43 - 08:53:53 Stayed for 0:22:10. Average temperature was 42. Opened: Driver door.
08:53:53 - 09:09:54 Drove for 0:16:01. Traveled 20.0 km, with average speed 83, EngineRPM 2153 and temperature 37.
09:09:54 - 13:49:42 Stayed for 4:39:48. Average temperature was 49. Opened: Driver door, Passenger door, Left rear door.
13:49:42 - 13:50:17 Drove for 0:00:35. Traveled 0.0 km, with average speed 11, EngineRPM 834 and temperature 50.
13:50:17 - 14:14:20 Stayed for 0:24:03. Average temperature was 50. Opened: Driver door, Passenger door, Left rear door.
14:14:20 - 14:15:51 Drove for 0:01:31. Traveled 0.0 km, with average speed 4, EngineRPM 865 and temperature 50.
14:15:51 - 14:23:27 Stayed for 0:07:36. Average temperature was 48. Opened: Driver door, Passenger door, Right rear door.
14:23:27 - 15:03:51 Drove for 0:40:24. Traveled 28.0 km, with average speed 50, EngineRPM 1348 and temperature 38.
15:03:51 - 15:24:04 Stayed for 0:20:13. Average temperature was 37. Opened: Driver door.
15:24:04 - 15:37:13 Drove for 0:13:09. Traveled 6.0 km, with average speed 44, EngineRPM 1322 and temperature 37.
15:37:13 - 15:48:21 Stayed for 0:11:08. Average temperature was 35. Opened: Driver door.
15:48:21 - 18:16:33 Drove for 2:28:12. Traveled 260.0 km, with average speed 115, EngineRPM 2026 and temperature 32.
18:16:33 - 18:33:46 Stayed for 0:17:13. Average temperature was 30. Opened: Driver door, Passenger door, Left rear door.
18:33:46 - 18:34:38 Drove for 0:00:52. Traveled 0.0 km, with average speed 22, EngineRPM 1069 and temperature 29.
18:34:38 - midnight Stayed till the end of the day. Average temperature was 44. Opened: Driver door.
Out[26]: