For this week’s Data Dive, I continued working with the Seoul Bike Sharing Demand dataset. My goal is to investigate different groups in the data and understand how their frequencies can be interpreted in terms of probability. I will also look for groups that occur less frequently and consider possible reasons why they are rare.
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.1 ✔ readr 2.2.0
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.3 ✔ tibble 3.3.1
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.2
## ✔ purrr 1.2.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
bike <- read.csv("SeoulBikeData.csv", fileEncoding = "latin1")
glimpse(bike)
## Rows: 8,760
## Columns: 14
## $ Date <chr> "01/12/2017", "01/12/2017", "01/12/2017", "0…
## $ Rented.Bike.Count <int> 254, 204, 173, 107, 78, 100, 181, 460, 930, …
## $ Hour <int> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13…
## $ Temperature..C. <dbl> -5.2, -5.5, -6.0, -6.2, -6.0, -6.4, -6.6, -7…
## $ Humidity... <int> 37, 38, 39, 40, 36, 37, 35, 38, 37, 27, 24, …
## $ Wind.speed..m.s. <dbl> 2.2, 0.8, 1.0, 0.9, 2.3, 1.5, 1.3, 0.9, 1.1,…
## $ Visibility..10m. <int> 2000, 2000, 2000, 2000, 2000, 2000, 2000, 20…
## $ Dew.point.temperature..C. <dbl> -17.6, -17.6, -17.7, -17.6, -18.6, -18.7, -1…
## $ Solar.Radiation..MJ.m2. <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.…
## $ Rainfall.mm. <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ Snowfall..cm. <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ Seasons <chr> "Winter", "Winter", "Winter", "Winter", "Win…
## $ Holiday <chr> "No Holiday", "No Holiday", "No Holiday", "N…
## $ Functioning.Day <chr> "Yes", "Yes", "Yes", "Yes", "Yes", "Yes", "Y…
names(bike)
## [1] "Date" "Rented.Bike.Count"
## [3] "Hour" "Temperature..C."
## [5] "Humidity..." "Wind.speed..m.s."
## [7] "Visibility..10m." "Dew.point.temperature..C."
## [9] "Solar.Radiation..MJ.m2." "Rainfall.mm."
## [11] "Snowfall..cm." "Seasons"
## [13] "Holiday" "Functioning.Day"
For my first grouping, I grouped the observations by season. I wanted to see how many observations belong to each season and the average number of rented bikes for each group.
season_group <- bike %>%
group_by(Seasons) %>%
summarise(
count = n(),
average_rentals = mean(Rented.Bike.Count)
) %>%
mutate(
probability = count / sum(count)
)
season_group
## # A tibble: 4 × 4
## Seasons count average_rentals probability
## <chr> <int> <dbl> <dbl>
## 1 Autumn 2184 820. 0.249
## 2 Spring 2208 730. 0.252
## 3 Summer 2208 1034. 0.252
## 4 Winter 2160 226. 0.247
The probability represents the chance that a randomly selected row from the dataset belongs to each season. ### Interpretation
Winter is the smallest group with 2,160 observations and a probability of about 24.66%. This means that if I randomly select one row from the dataset, it is slightly less likely to be a Winter observation than an observation from the other seasons.
I also noticed that Winter has the lowest average number of rented bikes at about 226, while Summer has the highest average at about 1,034. This suggests that season may have an important relationship with bike rental demand.
My hypothesis is that colder temperatures during Winter reduce bike rental demand. This can be tested by comparing temperature with rented bike counts and checking whether lower temperatures are associated with fewer rentals. ### Visualization
ggplot(season_group, aes(x = Seasons, y = average_rentals)) +
geom_col() +
labs(
title = "Average Bike Rentals by Season",
x = "Season",
y = "Average Rented Bike Count"
)
For my second grouping, I grouped the observations based on whether the day was a holiday. I wanted to see whether holidays occur less frequently in the dataset and whether bike rental demand is different on holidays.
holiday_group <- bike %>%
group_by(Holiday) %>%
summarise(
count = n(),
average_rentals = mean(Rented.Bike.Count)
) %>%
mutate(
probability = count / sum(count)
)
holiday_group
## # A tibble: 2 × 4
## Holiday count average_rentals probability
## <chr> <int> <dbl> <dbl>
## 1 Holiday 432 500. 0.0493
## 2 No Holiday 8328 715. 0.951
The probability represents the chance that a randomly selected row belongs to each holiday category.
I identified the holiday category with the lowest probability and tagged observations belonging to that category.
rarest_holiday <- holiday_group %>%
slice_min(probability, n = 1, with_ties = FALSE) %>%
pull(Holiday)
bike <- bike %>%
mutate(
rare_holiday = if_else(Holiday == rarest_holiday, 1L, 0L)
)
rarest_holiday
## [1] "Holiday"
bike %>%
count(rare_holiday)
## rare_holiday n
## 1 0 8328
## 2 1 432
A value of 1 in rare_holiday means that the observation
belongs to the least common holiday category, while 0 means that it does
not.
ggplot(holiday_group, aes(x = Holiday, y = average_rentals)) +
geom_col() +
labs(
title = "Average Bike Rentals by Holiday Status",
x = "Holiday Status",
y = "Average Rented Bike Count"
)
My hypothesis is that holiday observations will be less common than non-holiday observations because there are fewer official holidays during a year. I also expect bike rental demand to be different on holidays because commuting and daily travel patterns may change. This can be tested by comparing the number of holiday and non-holiday observations and their average rented bike counts.
For my third grouping, I grouped the observations based on whether the bike rental system was functioning. I wanted to compare how frequently each category occurred and how rental activity differed between them.
function_group <- bike %>%
group_by(Functioning.Day) %>%
summarise(
count = n(),
average_rentals = mean(Rented.Bike.Count)
) %>%
mutate(
probability = count / sum(count)
)
function_group
## # A tibble: 2 × 4
## Functioning.Day count average_rentals probability
## <chr> <int> <dbl> <dbl>
## 1 No 295 0 0.0337
## 2 Yes 8465 729. 0.966
The probability represents the chance that a randomly selected observation belongs to each Functioning Day category.
ggplot(function_group, aes(x = Functioning.Day, y = average_rentals)) +
geom_col() +
labs(
title = "Average Bike Rentals by Functioning Day",
x = "Functioning Day",
y = "Average Rented Bike Count"
)
This grouping helps me see how often the bike rental system was functioning compared with times when it was not functioning.The non-functioning category is the smaller group, with 295 observations and a probability of about 3.37%. The average rented bike count is 0 when the system is not functioning, compared with about 729 when it is functioning. This shows that some unusually low rental observations are related to system availability rather than normal customer demand.
For this part, I selected Seasons and
Holiday. I wanted to investigate how frequently each
combination of season and holiday status occurs.
season_holiday <- bike %>%
count(Seasons, Holiday) %>%
mutate(
probability = n / sum(n)
) %>%
arrange(n)
season_holiday
## Seasons Holiday n probability
## 1 Summer Holiday 48 0.005479452
## 2 Spring Holiday 72 0.008219178
## 3 Autumn Holiday 120 0.013698630
## 4 Winter Holiday 192 0.021917808
## 5 Winter No Holiday 1968 0.224657534
## 6 Autumn No Holiday 2064 0.235616438
## 7 Spring No Holiday 2136 0.243835616
## 8 Summer No Holiday 2160 0.246575342
Summer + Holiday is the least common combination with 48 observations and a probability of about 0.55%, while Summer + No Holiday is the most common with 2,160 observations and a probability of about 24.66%. This shows that holiday observations are relatively rare across the dataset, particularly during Summer. All possible Season and Holiday combinations are present in the data.
all_combinations <- expand_grid(
Seasons = unique(bike$Seasons),
Holiday = unique(bike$Holiday)
)
missing_combinations <- all_combinations %>%
anti_join(
bike %>% distinct(Seasons, Holiday),
by = c("Seasons", "Holiday")
)
missing_combinations
## # A tibble: 0 × 2
## # ℹ 2 variables: Seasons <chr>, Holiday <chr>
If this table has zero rows, every possible combination of season and holiday status appears in the dataset. If any rows appear, those combinations are missing from the observed data.
season_holiday %>%
slice_min(n, n = 1, with_ties = FALSE)
## Seasons Holiday n probability
## 1 Summer Holiday 48 0.005479452
season_holiday %>%
slice_max(n, n = 1, with_ties = FALSE)
## Seasons Holiday n probability
## 1 Summer No Holiday 2160 0.2465753
This allows me to identify which combination has the lowest probability and which has the highest probability of being selected if I randomly choose one observation from the dataset.
ggplot(season_holiday,
aes(x = Seasons, y = n, fill = Holiday)) +
geom_col(position = "dodge") +
labs(
title = "Season and Holiday Combinations",
x = "Season",
y = "Number of Observations",
fill = "Holiday Status"
)
This visualization makes it easier to compare holiday and non-holiday observations within each season.
This analysis helped me understand probability using the frequency of groups in a real dataset. A group containing fewer observations has a lower probability of being selected when choosing a row randomly.
The seasonal analysis showed that the four seasons occur at fairly similar frequencies, although their average bike rental counts are different. The Holiday and Functioning Day groupings allow me to investigate categories that occur much less frequently and understand why those observations may appear unusual.
The combination analysis also helped me see that probability can be investigated across more than one categorical variable at the same time. Instead of looking only at individual rows, grouping the data made it easier to identify common and rare patterns.
One question I would investigate further is whether the differences in bike rental demand across these groups can be explained by continuous variables such as temperature, rainfall, or snowfall.