Chapter 1 Der Datensatz

Bevor wir Modelle zur Klassifikation der Daten nutzen, analysieren wir den Datensatz, bereiten die Daten für unsere Nutzung auf und bereinigen den Datensatz. Dazu laden wir die folgenden libraries in R.

# Import needed libraries
library(dplyr)
library(lubridate)
library(ggplot2)
library(ggthemes)
library(scales)
library(caTools)
library(MASS)
library(e1071)

Im nächsten Schritt werden die Daten in R importiert. Um die Berechnungen etwas zu beschleunigen, wird eine zufällige Stichprobe mit \(N =500.000\) gezogen und gespeichert. Durch die Funktion set.seed() lässt sich das Ziehen der Stichprobe reproduzieren.

# set directory with stored csv-files
# setwd("/home/.../daten")

# # initialize data frame
# bikedata = data.frame()
# 
# # extract file names
# csv_files = list.files()
# 
# # Import the data
# for(file in csv_files){bikedata = rbind(bikedata,read.table(file,header=T,sep=",",stringsAsFactors = F))}
# 
# draw sample
# set.seed(123)
# bikedata = sample_n(bikedata,500000)
# 
# # save sample
# write.csv(bikedata,"/home/daniel/sciebo/Vortrag_Bewerbung_ASV/daten/bikedata.csv", row.names = TRUE)

Im nächsten Schritt laden wir den gespeicherten Datensatz und werfen einen ersten Blick auf die Daten. Die Funktion head() wird genutzt, um die ersten Datensätze anzuzeigen.

# load sample
bikedata= read.table("bikedata.csv",header=T,sep=",",stringsAsFactors = F)

# look at the first data rows
head(bikedata)
##   X tripduration                starttime                 stoptime
## 1 1          904 2018-09-04 16:43:34.5150 2018-09-04 16:58:39.2420
## 2 2          691 2018-09-20 14:04:31.8180 2018-09-20 14:16:03.1310
## 3 3         1235 2018-11-14 22:59:08.1330 2018-11-14 23:19:43.3510
## 4 4          642 2018-06-12 10:59:21.3640 2018-06-12 11:10:03.9700
## 5 5          284 2018-12-05 15:27:02.6660 2018-12-05 15:31:47.0700
## 6 6          467 2018-01-24 08:05:57.3790 2018-01-24 08:13:44.7510
##   start.station.id       start.station.name start.station.latitude
## 1              441          E 52 St & 2 Ave               40.75601
## 2              518          E 39 St & 2 Ave               40.74780
## 3             3539 W 116 St & Amsterdam Ave               40.80676
## 4              285       Broadway & E 14 St               40.73455
## 5              490          8 Ave & W 33 St               40.75155
## 6              519    Pershing Square North               40.75187
##   start.station.longitude end.station.id    end.station.name
## 1               -73.96742            492     W 33 St & 7 Ave
## 2               -73.97344            236 St Marks Pl & 2 Ave
## 3               -73.96071           3145  E 84 St & Park Ave
## 4               -73.99074            379     W 31 St & 7 Ave
## 5               -73.99393            362  Broadway & W 37 St
## 6               -73.97771            507     E 25 St & 2 Ave
##   end.station.latitude end.station.longitude bikeid   usertype birth.year
## 1             40.75020             -73.99093  25661 Subscriber       1984
## 2             40.72842             -73.98714  15331 Subscriber       1982
## 3             40.77863             -73.95772  34119 Subscriber       1987
## 4             40.74916             -73.99160  31092 Subscriber       1973
## 5             40.75173             -73.98754  15967 Subscriber       1988
## 6             40.73913             -73.97974  16050 Subscriber       1990
##   gender
## 1      1
## 2      1
## 3      1
## 4      1
## 5      1
## 6      2

1.1 Erstellen neuer Variablen

Einige der Variablen des Datensatzes sind ohne eine Bearbeitung nicht für die Klassifikationsmodelle, die im nächsten Abschnitt diskutiert werden, nutzbar. Aus diesem Grund nutzen wir die gegebenen Informationen, um neue Variablen zu bestimmen.

Die Informationen in Starttime, mit Ausprägungen wie 2018-09-04 16:43:34.5150, sind für die Analyse sehr nützlich. Im Folgenden bestimmen wir aus Starttime den Wochentag, den Monat und die Uhrzeit der Ausleihe. Zusätzlich wird die Dauer der Fahrt in Minuten bestimmt sowie das Alter der Kunden.

# create tripDate field
bikedata$tripDate = as.Date(bikedata$starttime,format="%Y-%m-%d")

# convert character to POSIXct
bikedata$time = as.POSIXct(strptime(bikedata$starttime,"%Y-%m-%d %H:%M:%S"))

# extract time
bikedata$time = strftime(bikedata$time,format="%H:%M:%S")

# new variables
bikedata$wday = wday(bikedata$tripDate)
bikedata$month = factor(month(bikedata$tripDate))
bikedata$hour = as.numeric(substr(bikedata$time,1,2))
bikedata$tripMin = bikedata$tripduration/60
bikedata$age = year(today())-bikedata$birth.year
bikedata$age_group = cut(bikedata$age,breaks=c(0,29,39,49,59,69,79,100),labels=c("unter 30","< 40","< 50","< 60","< 70","< 80","80 und älter"))

Im nächsten Schritt bekommen einige Variablen einfach zu interpretierende Label.

#convert gender vars to name
bikedata$gender = ifelse(bikedata$gender==1,"Männer",ifelse(bikedata$gender==2,"Frauen","Unbekannt"))



# rename weekdays
bikedata$wday = ifelse(bikedata$wday==1,"Sonntag",bikedata$wday)
bikedata$wday = ifelse(bikedata$wday==2,"Montag",bikedata$wday)
bikedata$wday = ifelse(bikedata$wday==3,"Dienstag",bikedata$wday)
bikedata$wday = ifelse(bikedata$wday==4,"Mittwoch",bikedata$wday)
bikedata$wday = ifelse(bikedata$wday==5,"Donnerstag",bikedata$wday)
bikedata$wday = ifelse(bikedata$wday==6,"Freitag",bikedata$wday)
bikedata$wday = ifelse(bikedata$wday==7,"Samstag",bikedata$wday)
bikedata$wday = factor(bikedata$wday,levels = c("Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag","Sonntag"))

# usertype as factor
bikedata$usertype = factor(bikedata$usertype)

Aus den Informationen in start.station.latitude, start.station.longitude, end.station.latitude und end.station.longitude wird die Distanz (Luftlinie) zwischen Start und Endstation bestimmt. Dazu definieren wir folgende Funktionen:

# calculate distance between start and end stations
getDistanceFromLatLonInKm = function(lat1,lon1,lat2,lon2) {
  R = 6371                     # Radius of the earth in km
  dLat = deg2rad(lat2-lat1)    # deg2rad below
  dLon = deg2rad(lon2-lon1)
  a = sin(dLat/2) * sin(dLat/2) +
      cos(deg2rad(lat1)) * cos(deg2rad(lat2)) *
      sin(dLon/2) * sin(dLon/2)

  c = 2 * atan2(sqrt(a), sqrt(1-a))
  d = R * c                    # Distance in km
  return(d)
}

deg2rad = function(deg) {
  return(deg * (pi/180))
}

bikedata$dist=getDistanceFromLatLonInKm(bikedata$start.station.latitude,bikedata$start.station.longitude,
                                        bikedata$end.station.latitude,bikedata$end.station.longitude)

1.2 Bereinigung der Daten

Einige Variablen haben Ausprägungen, die wahrscheinlich aus fehlerhaften Eintragungen resultieren. Um die Schätzung unabhängig von diesen Beobachtungen zu machen, werden die betroffenen Beobachtungen aus dem Datensatz entfernt.

Wir beginnen mit tripMin. Durch die summary() Funktion können wir uns den maximalen Wert dieser Variable anzeigen lassen. Der maximale Wert entspricht ca. 1863 Stunden. Dieser Wert scheint nicht realistisch. Wir entfernen alle Ausleihen, die länger als 12 Stunden andauern.

# check tripduration
summary(bikedata$tripMin)
##      Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
##      1.02      5.97     10.08     16.06     17.65 111781.68
long_trip = bikedata$tripMin < 720
sum(long_trip == FALSE)
## [1] 222
bikedata= bikedata[bikedata$tripMin < 720,]

Für die Variable Alter entfernen wir alle Beobachtungen mit einem Alter 90 oder älter. Der maximale Wert von 136 scheint auch hier nicht realistisch.

# check age
summary(bikedata$age)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   19.00   32.00   40.00   41.99   52.00  136.00
too_old = bikedata$age < 90
sum(too_old == FALSE)
## [1] 413
bikedata= bikedata[bikedata$age< 90,]

Die Variable dist hat den maximalen Wert 14.83 km. Hier ist keine Bereinigung nötig. Zuletzt überprüfen wir, ob es Variablen mit missing values gibt, das ist hier aber nicht gegeben.

# check trip distance
summary(bikedata$dist)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.0000  0.8271  1.3734  1.7857  2.3031 14.8286
# check for missing values
miss.val = is.na(bikedata)
sum(miss.val == TRUE)
## [1] 0

1.3 Deskriptive Analyse

In diesem Abschnitt führen wir eine deskriptive Analyse des Datensatzes durch, um die Variablen des Datensatzes besser zu verstehen und in den Klassifikationsmodellen benutzen zu können.

# gender
bikedata %>% ggplot(aes(x=gender,fill=gender)) + geom_bar(alpha=.8) + theme_fivethirtyeight() + scale_fill_brewer(palette="Set2")+
             theme(legend.position="none")+ggtitle(expression(atop("68% der Citi Bike Nutzer sind Männer")))+
             scale_y_continuous(label=unit_format(big.mark = ".",decimal.mark = ",",suffix=""))

# age
bikedata %>% ggplot(aes(x=age)) + geom_density(bw=1.5)+theme_fivethirtyeight()+theme(axis.text.y=element_blank())+ggtitle("Altersverteilung der Citi Biker")+
             geom_vline(xintercept=median(bikedata$age,na.rm=T),linetype="dashed",col="dark grey")+annotate("text",x=33,y=.045,label="Median = 40")

# age_group
bikedata %>% ggplot(aes(x=age_group,fill=age_group)) + geom_bar(alpha=.8)+theme_fivethirtyeight()+scale_fill_brewer(palette="Set2")+
             theme(legend.position="false")+ggtitle(expression(atop("Verteilung nach Altersgruppen",atop("50% sind 39 oder jünger"))))+
             scale_y_continuous(label=unit_format(big.mark = ".",decimal.mark = ",",suffix=""))

# age by gender
bikedata %>% filter(gender %in% c("Männer","Frauen")) %>% ggplot(aes(x=gender,y=age,fill=gender))+geom_boxplot(alpha=.4)+ylim(15,90)+theme_fivethirtyeight()+
             theme(legend.position="none")+scale_fill_brewer(palette="Set2")+ggtitle("Altersverteilung nach Geschlecht")+annotate("text",x="Frauen",y=39,label="Median = 37")+
             annotate("text",x="Männer",y=41,label="Median = 39")

# usertype
bikedata %>% ggplot(aes(x=usertype,fill=usertype)) + geom_bar(alpha=.7)+theme_fivethirtyeight()+theme(legend.position="none")+
             ggtitle(expression(atop("Nutzertypen",atop("Customer = 24 hour/3 Day pass  | Subscriber = Annual Pass")))) +
             scale_y_continuous(label=unit_format(big.mark = ".",decimal.mark=",",suffix=""))+
             annotate("text",x="Subscriber",y=350000,label="89% sind Subscriber")

# rides by day
bikedata %>% ggplot(aes(x=wday,fill=wday))+geom_bar(alpha=.8)+theme_fivethirtyeight()+ scale_fill_brewer(palette="Set2")+theme(legend.position="none")+
             ggtitle(expression(atop("In der Woche höhere Nutzung",atop("Anzahl Fahrten pro Tag"))))

# weekday distribution, graph
bikedata %>% ggplot(aes(x=hour,fill=factor(wday))) + geom_density(alpha=.2)+facet_wrap(~wday,ncol=1)+theme_fivethirtyeight()+
             theme(legend.position="none",axis.text.y=element_blank(),plot.title=element_text(hjust=.5)) +
              ggtitle(expression(atop("Zeitliche Verteilung der Nutzung",atop("In der Woche gibt es morgens (8:00-9:00) and abends (17:00-18:00) Spitzen"))))

# weekday distribution
bikedata %>% ggplot(aes(x=wday,y=tripMin,fill=wday))+geom_boxplot(alpha=.5)+ylim(0,30)+theme_fivethirtyeight()+scale_fill_brewer(palette="Set2")+
             theme(legend.position="none")+ggtitle("Am Wochenende werden die Fahrräder länger genutzt")

#time of day distribution by usertype
bikedata %>% ggplot(aes(x=hour,group=usertype,fill=usertype)) + geom_density(alpha=.3) + theme_fivethirtyeight() +
             theme(legend.title=element_blank(),axis.text.y=element_blank())+
             ggtitle(expression(atop("Nutzertypen unterscheiden sich in zeitlicher Nutzung",atop("Customer = 24 hour/3 Day pass  | Subscriber = Annual Pass"))))

#time of day distribution by usertype, weekday
bikedata %>% filter(wday %in% c("Montag","Dienstag","Sonntag")) %>% ggplot(aes(x=hour,group=usertype,fill=usertype)) + geom_density(alpha=.3) + facet_wrap(~wday,ncol=1) + theme_fivethirtyeight() +
             theme(legend.title=element_blank(),axis.text.y=element_blank(),plot.title=element_text(hjust=.5)) +
             ggtitle(expression(atop("Subscribers nutzen Citi Bikes für den Weg zur Arbeit")))

# trip duration by usertype
bikedata %>% ggplot(aes(x=usertype,y=tripMin,fill=usertype))+geom_boxplot(alpha=.5)+ylim(0,70)+theme_fivethirtyeight()+
             theme(legend.position="none")+ggtitle(expression(atop("Subscribers nutzen die Fahrräder kürzer",atop("Minuten pro Nutzung"))))+
             annotate("text",x="Customer",y=23,label="Median = 21,4")+annotate("text",x="Subscriber",y=11.5,label="Median = 9,3")

# weekday distribution, dist
bikedata %>% ggplot(aes(x=dist,fill=factor(wday))) + geom_density(alpha=.2)+facet_wrap(~wday,ncol=1)+theme_fivethirtyeight()+
  theme(legend.position="none",axis.text.y=element_blank(),plot.title=element_text(hjust=.5)) +
  ggtitle(expression(atop("Abstand zwischen Start- und Endstation",atop("Keine Unterschiede zwischen Arbeitstagen und Wochenende"))))

#dist by usertype
bikedata %>% ggplot(aes(x=dist,group=usertype,fill=usertype)) + geom_density(alpha=.3) + theme_fivethirtyeight() +
  theme(legend.title=element_blank(),axis.text.y=element_blank())+
  ggtitle(expression(atop("Nutzertypen unterscheiden sich in gefahrener Distanz",atop("Customer = 24 hour/3 Day pass  | Subscriber = Annual Pass"))))

#dist by usertype, weekday
bikedata %>% filter(wday %in% c("Montag","Dienstag","Sonntag")) %>% ggplot(aes(x=dist,group=usertype,fill=usertype)) + geom_density(alpha=.3) + facet_wrap(~wday,ncol=1) + theme_fivethirtyeight() +
  theme(legend.title=element_blank(),axis.text.y=element_blank(),plot.title=element_text(hjust=.5))+
  ggtitle(expression(atop("Nutzertypen unterscheiden sich in gefahrener Distanz",atop("Customer = 24 hour/3 Day pass  | Subscriber = Annual Pass"))))