|
|
import streamlit as st |
|
|
import pandas as pd |
|
|
import numpy as np |
|
|
import plotly.express as px |
|
|
import plotly.graph_objects as go |
|
|
|
|
|
|
|
|
st.set_page_config( |
|
|
page_title="Getaround Delay Analysis", |
|
|
page_icon="🚗", |
|
|
layout="wide" |
|
|
) |
|
|
|
|
|
|
|
|
st.title("🚗 Getaround Rental Delay Analysis") |
|
|
|
|
|
|
|
|
@st.cache_data |
|
|
def load_data(): |
|
|
try: |
|
|
|
|
|
url = 'https://full-stack-assets.s3.eu-west-3.amazonaws.com/Deployment/get_around_delay_analysis.xlsx' |
|
|
|
|
|
try: |
|
|
st.write(f"Trying to load data from URL: {url}") |
|
|
df = pd.read_excel(url) |
|
|
st.success(f"Successfully loaded data from URL") |
|
|
except Exception as e: |
|
|
st.error(f"Error loading data from URL: {e}") |
|
|
|
|
|
|
|
|
st.success("Data loaded successfully!") |
|
|
|
|
|
|
|
|
df['time_delta_with_previous_rental_in_minutes'] = df['time_delta_with_previous_rental_in_minutes'].fillna(721) |
|
|
|
|
|
|
|
|
bins = [-np.inf, 30, 60, 180, 720, np.inf] |
|
|
labels = ['1. <30 minutes', '2. 30-60 minutes', '3. 1-3 hours', '4. 3-12 hours', '5. >12 hours'] |
|
|
df['time_vs_previous_rental_category'] = pd.cut( |
|
|
df['time_delta_with_previous_rental_in_minutes'], |
|
|
bins=bins, |
|
|
labels=labels, |
|
|
right=False |
|
|
) |
|
|
|
|
|
|
|
|
df.loc[df['delay_at_checkout_in_minutes'] < 0, 'checkout_status'] = 'Late' |
|
|
df.loc[df['delay_at_checkout_in_minutes'] >= 0, 'checkout_status'] = 'On time' |
|
|
df.loc[df['delay_at_checkout_in_minutes'].isna(), 'checkout_status'] = 'On time' |
|
|
|
|
|
|
|
|
bins_checkout = [-np.inf, -720, -120, -60, -30, 0, 30, 60, np.inf] |
|
|
labels_checkout = ['1. >12h late', '2. 2-12h late', '3. 1-2h late', '4. 30-60min late', |
|
|
'5. <30min late', '6. <30min early', '7. 30-60min early', '8. >1h early'] |
|
|
df['checkout_delay_category'] = pd.cut( |
|
|
df['delay_at_checkout_in_minutes'], |
|
|
bins=bins_checkout, |
|
|
labels=labels_checkout, |
|
|
right=False |
|
|
) |
|
|
|
|
|
|
|
|
car_rental_counts = df['car_id'].value_counts().reset_index() |
|
|
car_rental_counts.columns = ['car_id', 'rental_count'] |
|
|
|
|
|
|
|
|
def categorize_frequency(count): |
|
|
if count == 1: |
|
|
return '1 rental' |
|
|
elif 2 <= count <= 3: |
|
|
return '2-3 rentals' |
|
|
elif 4 <= count <= 5: |
|
|
return '4-5 rentals' |
|
|
elif 6 <= count <= 10: |
|
|
return '6-10 rentals' |
|
|
else: |
|
|
return '>10 rentals' |
|
|
|
|
|
car_rental_counts['rental_frequency_category'] = car_rental_counts['rental_count'].apply(categorize_frequency) |
|
|
|
|
|
|
|
|
df = df.merge(car_rental_counts[['car_id', 'rental_frequency_category']], on='car_id', how='left') |
|
|
|
|
|
|
|
|
df = df.merge( |
|
|
df[['rental_id', 'delay_at_checkout_in_minutes']], |
|
|
left_on='previous_ended_rental_id', |
|
|
right_on='rental_id', |
|
|
how='left', |
|
|
suffixes=('', '_previous') |
|
|
).rename(columns={'delay_at_checkout_in_minutes_previous': 'delay_previous_rental'}) |
|
|
|
|
|
df['gap_between_checkin_chekout']=df['time_delta_with_previous_rental_in_minutes']-df['delay_previous_rental'] |
|
|
df['late_checkin'] = '' |
|
|
bins = [-np.inf, 0, np.inf] |
|
|
|
|
|
labels = ['Late', 'Not Late'] |
|
|
|
|
|
df['late_checkin'] = pd.cut( |
|
|
df['gap_between_checkin_chekout'], |
|
|
bins=bins, |
|
|
labels=labels, |
|
|
right=False |
|
|
) |
|
|
|
|
|
return df |
|
|
|
|
|
except Exception as e: |
|
|
st.error(f"Error loading data: {e}") |
|
|
return None |
|
|
|
|
|
|
|
|
df = load_data() |
|
|
|
|
|
if df is not None: |
|
|
|
|
|
tab0, tab1, tab2, tab3 = st.tabs(["Key Insights", "General Analysis", "Late Checkout Impact", "Threshold Analysis"]) |
|
|
|
|
|
|
|
|
with tab0: |
|
|
st.header("Key Insights") |
|
|
|
|
|
|
|
|
total_rentals = len(df) |
|
|
connect_rentals = len(df[df['checkin_type'] == 'connect']) |
|
|
mobile_rentals = len(df[df['checkin_type'] == 'mobile']) |
|
|
connect_pct = connect_rentals / total_rentals * 100 |
|
|
mobile_pct = mobile_rentals / total_rentals * 100 |
|
|
|
|
|
late_checkouts = len(df[df['checkout_status'] == 'Late']) |
|
|
late_checkout_pct = late_checkouts / total_rentals * 100 |
|
|
|
|
|
canceled_rentals = len(df[df['state'] == 'canceled']) |
|
|
canceled_pct = canceled_rentals / total_rentals * 100 |
|
|
|
|
|
late_checkins = len(df[df['late_checkin'] == 'Late']) |
|
|
|
|
|
|
|
|
st.subheader("Rental Overview") |
|
|
col1, col2, col3 = st.columns(3) |
|
|
with col1: |
|
|
st.metric("Total Rentals", f"{total_rentals:,}") |
|
|
with col2: |
|
|
st.metric("Connect Rentals", f"{connect_rentals:,} ({connect_pct:.1f}%)") |
|
|
with col3: |
|
|
st.metric("Mobile Rentals", f"{mobile_rentals:,} ({mobile_pct:.1f}%)") |
|
|
|
|
|
st.subheader("Delay Impact") |
|
|
col1, col2, col3 = st.columns(3) |
|
|
with col1: |
|
|
st.metric("Late Checkouts", f"{late_checkouts:,} ({late_checkout_pct:.1f}%)") |
|
|
with col2: |
|
|
st.metric("Canceled Rentals", f"{canceled_rentals:,} ({canceled_pct:.1f}%)") |
|
|
with col3: |
|
|
st.metric("Late Check-ins due to Previous Rental", f"{late_checkins:,}") |
|
|
|
|
|
|
|
|
st.markdown(""" |
|
|
### Key Findings |
|
|
|
|
|
1. **Short time gaps between reservations represent a minor portion of business operations**: |
|
|
- Out of 21k rentals, only 8% have a time gap below 12 hours between consecutive rentals |
|
|
- On average, each car is rented fewer than 3 times, indicating moderate utilization |
|
|
- Less than 400 rentals (approximately 2%) have a time gap below 1 hour from the previous rental |
|
|
|
|
|
2. **Late checkouts have limited impact on overall business operations**: |
|
|
- Only 218 rentals were affected by late checkouts, where the car was not available at the scheduled time |
|
|
- The cancellation rate for affected rentals is around 17%, which is comparable to the average cancellation rate of 15% |
|
|
- Most delays were under 30 minutes, likely due to minor traffic issues, which wouldn't typically justify a cancellation |
|
|
|
|
|
3. **A buffer of 30-60 minutes between rentals appears sufficient to minimize scheduling conflicts**: |
|
|
- Given the current rental frequency, aggressive time optimization does not appear necessary |
|
|
- Most delays are less than 1 hour, and this buffer would prevent most potential issues |
|
|
- Approximately 2% of reservations would be affected by implementing this threshold |
|
|
""") |
|
|
|
|
|
|
|
|
with tab1: |
|
|
st.header("General Analysis") |
|
|
|
|
|
|
|
|
st.subheader("Key Figures") |
|
|
|
|
|
total_rentals = len(df) |
|
|
close_rentals = len(df[df['time_delta_with_previous_rental_in_minutes'] < 720]) |
|
|
avg_rentals_per_car = df['car_id'].value_counts().mean() |
|
|
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
with col1: |
|
|
st.metric("Total Rentals", f"{total_rentals:,}") |
|
|
with col2: |
|
|
st.metric("% Rentals with <12h Gap between 2 rentals", f"{close_rentals/total_rentals:.1%}") |
|
|
with col3: |
|
|
st.metric("Avg. Rentals per Car", f"{avg_rentals_per_car:.1f}") |
|
|
|
|
|
|
|
|
st.subheader("Column Distribution") |
|
|
allowed_columns = [ |
|
|
'checkin_type', |
|
|
'state', |
|
|
'time_vs_previous_rental_category', |
|
|
'checkout_status', |
|
|
'checkout_delay_category', |
|
|
'rental_frequency_category' |
|
|
] |
|
|
selected_column = st.selectbox("Select column to visualize", allowed_columns) |
|
|
|
|
|
|
|
|
if pd.api.types.is_numeric_dtype(df[selected_column]): |
|
|
fig = px.histogram( |
|
|
df, |
|
|
x=selected_column, |
|
|
title=f"Distribution of {selected_column}" |
|
|
) |
|
|
else: |
|
|
|
|
|
value_counts = df[selected_column].value_counts().reset_index() |
|
|
value_counts.columns = ['Value', 'Count'] |
|
|
fig = px.bar( |
|
|
value_counts, |
|
|
x='Value', |
|
|
y='Count', |
|
|
title=f"Distribution of {selected_column}" |
|
|
) |
|
|
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
|
|
|
st.subheader("Time Between Consecutive Rentals by State") |
|
|
|
|
|
|
|
|
filtered_df = df |
|
|
|
|
|
|
|
|
time_state_dist = filtered_df.groupby(['time_vs_previous_rental_category', 'state']).size().reset_index() |
|
|
time_state_dist.columns = ['Time Category', 'State', 'Count'] |
|
|
|
|
|
|
|
|
time_totals = filtered_df.groupby('time_vs_previous_rental_category').size().reset_index() |
|
|
time_totals.columns = ['Time Category', 'Total'] |
|
|
|
|
|
|
|
|
time_state_dist = time_state_dist.merge(time_totals, on='Time Category') |
|
|
time_state_dist['Percentage'] = time_state_dist['Count'] / time_state_dist['Total'] * 100 |
|
|
|
|
|
|
|
|
fig = px.bar( |
|
|
time_state_dist, |
|
|
x='Time Category', |
|
|
y='Percentage', |
|
|
color='State', |
|
|
barmode='stack', |
|
|
text=time_state_dist['Percentage'].round(1), |
|
|
title="Distribution of Time Between Consecutive Rentals by State", |
|
|
labels={'Percentage': 'Percentage (%)'} |
|
|
) |
|
|
fig.update_traces(texttemplate='%{text}%', textposition='inside') |
|
|
fig.update_layout(uniformtext_minsize=8, uniformtext_mode='hide') |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
|
|
|
st.subheader("Time Between Consecutive Rentals by Type") |
|
|
|
|
|
|
|
|
filtered_df = df |
|
|
|
|
|
|
|
|
time_state_dist = filtered_df.groupby(['time_vs_previous_rental_category', 'checkin_type']).size().reset_index() |
|
|
time_state_dist.columns = ['Time Category', 'Type', 'Count'] |
|
|
|
|
|
|
|
|
time_totals = filtered_df.groupby('time_vs_previous_rental_category').size().reset_index() |
|
|
time_totals.columns = ['Time Category', 'Total'] |
|
|
|
|
|
|
|
|
time_state_dist = time_state_dist.merge(time_totals, on='Time Category') |
|
|
time_state_dist['Percentage'] = time_state_dist['Count'] / time_state_dist['Total'] * 100 |
|
|
|
|
|
|
|
|
fig = px.bar( |
|
|
time_state_dist, |
|
|
x='Time Category', |
|
|
y='Percentage', |
|
|
color='Type', |
|
|
barmode='stack', |
|
|
text=time_state_dist['Percentage'].round(1), |
|
|
title="Distribution of Time Between Consecutive Rentals by Type", |
|
|
labels={'Percentage': 'Percentage (%)'} |
|
|
) |
|
|
fig.update_traces(texttemplate='%{text}%', textposition='inside') |
|
|
fig.update_layout(uniformtext_minsize=8, uniformtext_mode='hide') |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
with tab2: |
|
|
|
|
|
st.subheader("Late Checkouts and Cancellations") |
|
|
|
|
|
|
|
|
rentals_with_prev = df.dropna(subset=['previous_ended_rental_id']) |
|
|
total_with_prev = len(rentals_with_prev) |
|
|
|
|
|
|
|
|
late_checkouts = rentals_with_prev[rentals_with_prev['checkout_status'] == 'Late'] |
|
|
num_late_checkouts = len(late_checkouts) |
|
|
|
|
|
|
|
|
pct_rental_with_infom_previous_rental = total_with_prev / total_rentals * 100 |
|
|
|
|
|
|
|
|
canceled_after_late = rentals_with_prev[(rentals_with_prev['checkout_status'] == 'Late') & |
|
|
(rentals_with_prev['state'] == 'canceled')] |
|
|
pct_canceled_after_late = len(canceled_after_late) / num_late_checkouts * 100 if num_late_checkouts > 0 else 0 |
|
|
|
|
|
|
|
|
number_late_checking = df[df['late_checkin'] == "Late"] |
|
|
|
|
|
|
|
|
st.markdown("### Key Figures") |
|
|
col1, col2, col3 = st.columns(3) |
|
|
with col1: |
|
|
st.metric("Rentals with Previous Rental Info", f"{total_with_prev:,}") |
|
|
with col2: |
|
|
st.metric("Percentage of Rental with Previous Rental", f"{pct_rental_with_infom_previous_rental:.1f}%") |
|
|
with col3: |
|
|
st.metric("Number of Rental with Late Checkin due to Previous Rental", f"{len(number_late_checking):,}") |
|
|
|
|
|
st.markdown("### Rental State depending on Late Checkout") |
|
|
|
|
|
|
|
|
grouped = df.groupby(['late_checkin', 'state'], observed=True)['rental_id'].count().reset_index() |
|
|
grouped.rename(columns={'rental_id': 'count'}, inplace=True) |
|
|
|
|
|
|
|
|
sum_grouped = df.groupby(['late_checkin'], observed=True)['rental_id'].count().reset_index() |
|
|
sum_grouped.rename(columns={'rental_id': 'sum'}, inplace=True) |
|
|
|
|
|
|
|
|
result = pd.merge(grouped, sum_grouped, on='late_checkin') |
|
|
|
|
|
|
|
|
result['percentage'] = result['count']/result['sum']*100 |
|
|
|
|
|
|
|
|
fig = px.bar( |
|
|
result, |
|
|
x='late_checkin', |
|
|
y='count', |
|
|
color='state', |
|
|
barmode='stack', |
|
|
text=result['count'], |
|
|
title="Distribution of State by Type of Delay", |
|
|
labels={'count': 'Number of Rentals', 'late_checkin': 'Checkout Status', 'state': 'Rental State'} |
|
|
) |
|
|
fig.update_traces(texttemplate='%{text}', textposition='inside') |
|
|
fig.update_layout(uniformtext_minsize=8, uniformtext_mode='hide') |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
|
|
|
fig = px.bar( |
|
|
result, |
|
|
x='late_checkin', |
|
|
y='percentage', |
|
|
color='state', |
|
|
barmode='stack', |
|
|
text=result['percentage'].round(1), |
|
|
title="Percentage Distribution of State by Type of Delay", |
|
|
labels={'percentage': 'Percentage (%)', 'late_checkin': 'Checkout Status', 'state': 'Rental State'} |
|
|
) |
|
|
fig.update_traces(texttemplate='%{text}%', textposition='inside') |
|
|
fig.update_layout(uniformtext_minsize=8, uniformtext_mode='hide') |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("### Split of Rentals with Late Checkin by Checkout Delay Category") |
|
|
|
|
|
|
|
|
df_late = df[df['checkout_status'] == 'Late'] |
|
|
|
|
|
|
|
|
checkout_delay_counts = df_late.groupby('checkout_delay_category', observed=True)['rental_id'].count().reset_index() |
|
|
checkout_delay_counts.columns = ['Checkout Delay Category', 'Count'] |
|
|
|
|
|
|
|
|
total = checkout_delay_counts['Count'].sum() |
|
|
checkout_delay_counts['Percentage'] = checkout_delay_counts['Count'] / total * 100 |
|
|
|
|
|
|
|
|
checkout_delay_counts = checkout_delay_counts.sort_values('Checkout Delay Category') |
|
|
|
|
|
|
|
|
fig1 = px.bar( |
|
|
checkout_delay_counts, |
|
|
x='Checkout Delay Category', |
|
|
y='Count', |
|
|
text='Count', |
|
|
title="Number of Late Rentals by Checkout Delay Category", |
|
|
labels={ |
|
|
'Checkout Delay Category': 'Checkout Delay Category', |
|
|
'Count': 'Number of Rentals' |
|
|
}, |
|
|
color='Count', |
|
|
color_continuous_scale='Blues' |
|
|
) |
|
|
fig1.update_traces(texttemplate='%{text}', textposition='inside') |
|
|
fig1.update_layout( |
|
|
uniformtext_minsize=8, |
|
|
uniformtext_mode='hide', |
|
|
xaxis_title="Checkout Delay Category", |
|
|
yaxis_title="Number of Rentals", |
|
|
coloraxis_showscale=False |
|
|
) |
|
|
st.plotly_chart(fig1, use_container_width=True) |
|
|
|
|
|
|
|
|
fig2 = px.bar( |
|
|
checkout_delay_counts, |
|
|
x='Checkout Delay Category', |
|
|
y='Percentage', |
|
|
text=checkout_delay_counts['Percentage'].round(1), |
|
|
title="Percentage of Late Rentals by Checkout Delay Category", |
|
|
labels={ |
|
|
'Checkout Delay Category': 'Checkout Delay Category', |
|
|
'Percentage': 'Percentage (%)' |
|
|
}, |
|
|
color='Percentage', |
|
|
color_continuous_scale='Blues' |
|
|
) |
|
|
fig2.update_traces(texttemplate='%{text}%', textposition='inside') |
|
|
fig2.update_layout( |
|
|
uniformtext_minsize=8, |
|
|
uniformtext_mode='hide', |
|
|
xaxis_title="Checkout Delay Category", |
|
|
yaxis_title="Percentage of Rentals (%)", |
|
|
coloraxis_showscale=False |
|
|
) |
|
|
st.plotly_chart(fig2, use_container_width=True) |
|
|
|
|
|
|
|
|
|
|
|
with tab3: |
|
|
st.header("Threshold Analysis") |
|
|
|
|
|
|
|
|
threshold_options = [15, 30, 60, 90, 120, 180, 240, 300, 360] |
|
|
threshold = st.select_slider( |
|
|
"Select minimum delay threshold (minutes)", |
|
|
options=threshold_options, |
|
|
value=60 |
|
|
) |
|
|
|
|
|
st.markdown(f"### Impact of {threshold}-minute Minimum Delay") |
|
|
|
|
|
|
|
|
thresholds = list(range(0, 361, 30)) |
|
|
if threshold not in thresholds: |
|
|
thresholds.append(threshold) |
|
|
thresholds.sort() |
|
|
|
|
|
|
|
|
threshold_impact = [] |
|
|
for t in thresholds: |
|
|
all_affected = len(df[df['time_delta_with_previous_rental_in_minutes'] < t]) |
|
|
connect_affected = len(df[(df['checkin_type'] == 'connect') & |
|
|
(df['time_delta_with_previous_rental_in_minutes'] < t)]) |
|
|
mobile_affected = len(df[(df['checkin_type'] == 'mobile') & |
|
|
(df['time_delta_with_previous_rental_in_minutes'] < t)]) |
|
|
|
|
|
threshold_impact.append({ |
|
|
'threshold': t, |
|
|
'all_affected': all_affected, |
|
|
'connect_affected': connect_affected, |
|
|
'mobile_affected': mobile_affected, |
|
|
'all_pct': all_affected / len(df) * 100 if len(df) > 0 else 0, |
|
|
'connect_pct': connect_affected / len(df[df['checkin_type'] == 'connect']) * 100 |
|
|
if len(df[df['checkin_type'] == 'connect']) > 0 else 0, |
|
|
'mobile_pct': mobile_affected / len(df[df['checkin_type'] == 'mobile']) * 100 |
|
|
if len(df[df['checkin_type'] == 'mobile']) > 0 else 0 |
|
|
}) |
|
|
|
|
|
threshold_df = pd.DataFrame(threshold_impact) |
|
|
|
|
|
|
|
|
fig = px.line( |
|
|
threshold_df, |
|
|
x='threshold', |
|
|
y=['all_affected', 'connect_affected', 'mobile_affected'], |
|
|
labels={ |
|
|
'threshold': 'Minimum Delay Threshold (minutes)', |
|
|
'value': 'Number of Affected Rentals', |
|
|
'variable': 'Car Type' |
|
|
}, |
|
|
title="Number of Affected Rentals by Threshold" |
|
|
) |
|
|
|
|
|
|
|
|
newnames = {'all_affected': 'All Cars', 'connect_affected': 'Connect Cars', 'mobile_affected': 'Mobile Cars'} |
|
|
fig.for_each_trace(lambda t: t.update(name = newnames[t.name])) |
|
|
|
|
|
fig.update_layout(hovermode="x unified") |
|
|
|
|
|
|
|
|
fig.add_vline(x=threshold, line_dash="dash", line_color="red") |
|
|
fig.add_annotation(x=threshold, y=max(threshold_df['all_affected']), |
|
|
text=f"Selected: {threshold} min", |
|
|
showarrow=True, arrowhead=1, ax=30, ay=-30) |
|
|
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
|
|
|
fig = px.line( |
|
|
threshold_df, |
|
|
x='threshold', |
|
|
y=['all_pct', 'connect_pct', 'mobile_pct'], |
|
|
labels={ |
|
|
'threshold': 'Minimum Delay Threshold (minutes)', |
|
|
'value': 'Percentage of Affected Rentals (%)', |
|
|
'variable': 'Car Type' |
|
|
}, |
|
|
title="Percentage of Affected Rentals by Threshold" |
|
|
) |
|
|
|
|
|
|
|
|
newnames = {'all_pct': 'All Cars', 'connect_pct': 'Connect Cars', 'mobile_pct': 'Mobile Cars'} |
|
|
fig.for_each_trace(lambda t: t.update(name = newnames[t.name])) |
|
|
|
|
|
fig.update_layout(hovermode="x unified") |
|
|
|
|
|
|
|
|
fig.add_vline(x=threshold, line_dash="dash", line_color="red") |
|
|
fig.add_annotation(x=threshold, y=max(threshold_df['all_pct']), |
|
|
text=f"Selected: {threshold} min", |
|
|
showarrow=True, arrowhead=1, ax=30, ay=-30) |
|
|
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
|
|
|
st.subheader(f"Impact at Selected Threshold: {threshold} minutes") |
|
|
|
|
|
selected_row = threshold_df[threshold_df['threshold'] == threshold].iloc[0] if len(threshold_df[threshold_df['threshold'] == threshold]) > 0 else None |
|
|
|
|
|
if selected_row is not None: |
|
|
col1, col2, col3 = st.columns(3) |
|
|
with col1: |
|
|
st.metric("All Cars Affected", f"{int(selected_row['all_affected']):,}") |
|
|
with col2: |
|
|
st.metric("Connect Cars Affected", f"{int(selected_row['connect_affected']):,}") |
|
|
with col3: |
|
|
st.metric("Mobile Cars Affected", f"{int(selected_row['mobile_affected']):,}") |
|
|
|
|
|
|
|
|
affected_rentals = df[df['time_delta_with_previous_rental_in_minutes'] < threshold] |
|
|
|
|
|
if not affected_rentals.empty: |
|
|
st.subheader("Breakdown of Affected Rentals") |
|
|
|
|
|
|
|
|
checkin_breakdown = affected_rentals['checkin_type'].value_counts().reset_index() |
|
|
checkin_breakdown.columns = ['Check-in Type', 'Count'] |
|
|
checkin_breakdown['Percentage'] = checkin_breakdown['Count'] / len(affected_rentals) * 100 |
|
|
|
|
|
fig = px.pie( |
|
|
checkin_breakdown, |
|
|
values='Count', |
|
|
names='Check-in Type', |
|
|
title=f"Distribution of Affected Rentals by Check-in Type ({threshold} min threshold)", |
|
|
hole=0.4 |
|
|
) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
|
|
|
state_breakdown = affected_rentals['state'].value_counts().reset_index() |
|
|
state_breakdown.columns = ['State', 'Count'] |
|
|
state_breakdown['Percentage'] = state_breakdown['Count'] / len(affected_rentals) * 100 |
|
|
|
|
|
fig = px.bar( |
|
|
state_breakdown, |
|
|
x='State', |
|
|
y='Count', |
|
|
color='State', |
|
|
text_auto='.0f', |
|
|
title=f"State Distribution of Affected Rentals ({threshold} min threshold)" |
|
|
) |
|
|
fig.update_traces(textposition='outside') |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
else: |
|
|
st.error("Failed to load data. The app tried loading from the URL (https://full-stack-assets.s3.eu-west-3.amazonaws.com/Deployment/get_around_delay_analysis.xlsx) and local paths without success. Please check your internet connection or upload the file manually.") |
|
|
|
|
|
|
|
|
st.markdown("---") |
|
|
st.markdown("Getaround Rental Delay Analysis Dashboard - Developed by Louis Le Pogam") |