MTA icon set#
Used Claude.ai to add some data like color and RGB
import altair as alt
import polars as pl
data = [
{"name": "1train", "src": "https://i.imgur.com/5w147gb.png", "line": "1", "color": "Red", "rgb": "#EE352E"},
{"name": "2train", "src": "https://i.imgur.com/WbQXY6L.png", "line": "2", "color": "Red", "rgb": "#EE352E"},
{"name": "3train", "src": "https://i.imgur.com/vgF9PMQ.png", "line": "3", "color": "Red", "rgb": "#EE352E"},
{"name": "4train", "src": "https://i.imgur.com/fadogYd.png", "line": "4", "color": "Green", "rgb": "#00933C"},
{"name": "5train", "src": "https://i.imgur.com/K05HApQ.png", "line": "5", "color": "Green", "rgb": "#00933C"},
{"name": "6train", "src": "https://i.imgur.com/bRrwqkM.png", "line": "6", "color": "Green", "rgb": "#00933C"},
{"name": "7train", "src": "https://i.imgur.com/S9CVRlJ.png", "line": "7", "color": "Purple", "rgb": "#B933AD"},
{"name": "atrain", "src": "https://i.imgur.com/Uh6c4sD.png", "line": "A", "color": "Blue", "rgb": "#0039A6"},
{"name": "btrain", "src": "https://i.imgur.com/LA41bPi.png", "line": "B", "color": "Orange", "rgb": "#FF6319"},
{"name": "ctrain", "src": "https://i.imgur.com/SMIYfnv.png", "line": "C", "color": "Blue", "rgb": "#0039A6"},
{"name": "dtrain", "src": "https://i.imgur.com/b5sLbv0.png", "line": "D", "color": "Orange", "rgb": "#FF6319"},
{"name": "etrain", "src": "https://i.imgur.com/OM1OEUM.png", "line": "E", "color": "Blue", "rgb": "#0039A6"},
{"name": "ftrain", "src": "https://i.imgur.com/lvSg9sv.png", "line": "F", "color": "Orange", "rgb": "#FF6319"},
{"name": "gtrain", "src": "https://i.imgur.com/ur7mAno.png", "line": "G", "color": "Light Green", "rgb": "#6CBE45"},
{"name": "jtrain", "src": "https://i.imgur.com/3U4eNxl.png", "line": "JZ", "color": "Brown", "rgb": "#8B4513"},
{"name": "ltrain", "src": "https://i.imgur.com/S6NeJtj.png", "line": "L", "color": "Light Gray", "rgb": "#A7A9AC"},
{"name": "mtrain", "src": "https://i.imgur.com/0FZ6kum.png", "line": "M", "color": "Orange", "rgb": "#FF6319"},
{"name": "ntrain", "src": "https://i.imgur.com/Y5vukLP.png", "line": "N", "color": "Yellow", "rgb": "#FCCC0A"},
{"name": "qtrain", "src": "https://i.imgur.com/7BRayd1.png", "line": "Q", "color": "Yellow", "rgb": "#FCCC0A"},
{"name": "rtrain", "src": "https://i.imgur.com/zUGeskm.png", "line": "R", "color": "Yellow", "rgb": "#FCCC0A"},
{"name": "strain", "src": "https://i.imgur.com/1BJvHdC.png", "line": "S 42nd", "color": "Gray", "rgb": "#808080"},
{"name": "srocktrain", "src": "https://i.imgur.com/1BJvHdC.png", "line": "S Rock", "color": "Gray", "rgb": "#808080"},
{"name": "srklntrain", "src": "https://i.imgur.com/1BJvHdC.png", "line": "S Fkln", "color": "Gray", "rgb": "#808080"},
{"name": "wtrain", "src": "https://i.imgur.com/kUnS3Ko.png", "line": "W", "color": "Yellow", "rgb": "#FCCC0A"},
{"name": "ztrain", "src": "https://i.imgur.com/momyYjI.png", "line": "JZ", "color": "Brown", "rgb": "#8B4513"}
]
subway_train_data = pl.DataFrame(data)
subway_train_data.head(5)
shape: (5, 5)
name | src | line | color | rgb |
---|---|---|---|---|
str | str | str | str | str |
"1train" | "https://i.imgu… | "1" | "Red" | "#EE352E" |
"2train" | "https://i.imgu… | "2" | "Red" | "#EE352E" |
"3train" | "https://i.imgu… | "3" | "Red" | "#EE352E" |
"4train" | "https://i.imgu… | "4" | "Green" | "#00933C" |
"5train" | "https://i.imgu… | "5" | "Green" | "#00933C" |
(
alt.Chart(subway_train_data).mark_image(
width=20,
height=20
)
.encode(
x=alt.X('name:N').title(None),
y=alt.Y('color:N').title(None),
url='src:N',
)
)
(
alt.Chart(subway_train_data).mark_image(
width=20,
height=20
)
.encode(
x=alt.X('name:N').axis(ticks=False, labels=False).title(None),
y=alt.Y('color:N').axis(ticks=False, labels=False).title(None),
url='src:N',
row=alt.Row('color:N', spacing=0).title(None)
)
.resolve_scale(
x='independent',
y='independent'
)
)
(
alt.Chart(subway_train_data).mark_image(
width=20,
height=20
)
.encode(
x=alt.X('name:N').axis(ticks=False, labels=False).title(None),
y=alt.Y('color:N').axis(ticks=False, labels=False).title(None),
url='src:N',
row=alt.Row('color:N', spacing=0).title(None)
)
.resolve_scale(
x='independent',
y='independent'
)
)
alt.Chart(subway_train_data).mark_image(
width=20,
height=20
).encode(
x=alt.X('name:N').axis(labels=False, ticks=False, grid=False, domainWidth=0).title(None), # domainWith=0 + some hacking
y=alt.Y('color:N').axis(labels=False, ticks=False, grid=False, domainWidth=0).title(None),
color=alt.Color('letter:N').legend(None),
url='src:N',
row=alt.Row(
'color:N',
title=None,
header=alt.Header(labelFontSize=0),
sort=["Red", "Green", "Purple", "Blue", "Orange"],
spacing=2).title(None)
).resolve_scale(
x='independent',
y='independent'
)
subway_line_select = alt.selection_point(fields=['line'])
chart = alt.Chart(subway_train_data).mark_image(
width=20,
height=20
).encode(
x=alt.X('name:N').axis(labels=False, ticks=False, grid=False, domainWidth=0).title(None), # domainWith=0 + some hacking
y=alt.Y('color:N').axis(labels=False, ticks=False, grid=False, domainWidth=0).title(None),
color=alt.condition((subway_line_select), alt.Color('letter:N').legend(None), alt.value("lightgray")),
opacity=alt.condition((subway_line_select), alt.value(1), alt.value(0.3)),
url='src:N',
row=alt.Row(
'color:N',
title=None,
header=alt.Header(labelFontSize=0),
sort=["Red", "Green", "Purple", "Blue", "Orange"],
spacing=2).title(None)
).resolve_scale(
x='independent',
y='independent'
).add_params(subway_line_select)
chart
# hacky way to get rid of gridlines
# domainWith=0 gets rid of axis line!!!!
chart.view = {}
chart.view['strokeWidth'] = 0
chart.save("subway_final.html")
chart.properties(
title=alt.Title(
"Select Subway Line(s)",
subtitle=["Shift-Click to select Multiple", ""],
orient="top"))
MTA Data#
import polars as pl
import altair as alt
alt.data_transformers.disable_max_rows()
# Read the data and format the date
df = pl.read_csv("data/MTA_Subway_Wait_Assessment__2015-2019.csv").with_columns(
pl.col("month").str.to_date(format="%Y-%m")
).join(subway_train_data, on='line', how='left')
df.head()
shape: (5, 12)
month | line | division | day_type | period | num_timepoints_passing_wait _assessment | num_sched_timepoints | wait assessment | name | src | color | rgb |
---|---|---|---|---|---|---|---|---|---|---|---|
date | str | str | i64 | str | i64 | i64 | f64 | str | str | str | str |
2015-01-01 | "1" | "A DIVISION" | 1 | "offpeak" | 17864 | 22636 | 0.789185 | "1train" | "https://i.imgu… | "Red" | "#EE352E" |
2015-01-01 | "1" | "A DIVISION" | 1 | "peak" | 10540 | 15019 | 0.701778 | "1train" | "https://i.imgu… | "Red" | "#EE352E" |
2015-01-01 | "1" | "A DIVISION" | 2 | "offpeak" | 6601 | 7496 | 0.880603 | "1train" | "https://i.imgu… | "Red" | "#EE352E" |
2015-01-01 | "1" | "A DIVISION" | 2 | "peak" | 3177 | 3656 | 0.868982 | "1train" | "https://i.imgu… | "Red" | "#EE352E" |
2015-01-01 | "2" | "A DIVISION" | 1 | "offpeak" | 18357 | 24256 | 0.756802 | "2train" | "https://i.imgu… | "Red" | "#EE352E" |
import polars as pl
import altair as alt
# Altair by default lets you plot 5000 rows.
alt.data_transformers.disable_max_rows()
# Don't have a better way to do this
line_rgb_df = df.select('line', 'rgb').filter(pl.col("line")!="Systemwide").unique().to_pandas()
line_names = line_rgb_df['line'].to_list()
line_colors = line_rgb_df['rgb'].to_list()
longitudinal_mta = alt.Chart(df.to_pandas()).mark_line(opacity=0.5).encode(
x=alt.X("yearmonth(month):T").title("Month"),
y=alt.Y("wait assessment:Q").title("Wait Assessment (%)"),
column=alt.Column("period:N", header=alt.Header(labelFontSize=15)).title(None),
# color="line",
color=alt.condition(subway_line_select, alt.Color(field="line", scale=alt.Scale(domain=line_names, range=line_colors)).legend(None), alt.value("lightgray")),
opacity=alt.condition(subway_line_select, alt.value(0.5), alt.value(0.1)),
).transform_filter(
(alt.datum.line != "Systemwide") & (alt.datum.day_type==1)
).properties(
title=alt.Title(
"MTA: 2015-2019 Percent of Train times meeting 'Wait assessment' metric",
subtitle=["Weekdays"],
# subtitle="'Wait Assessment' refers to how 'regularly spaced' the trains are."
),
width=200,
height=150
).add_params(subway_line_select)
longitudinal_mta
wait_vs_timepoints = alt.Chart(df.to_pandas()).mark_circle(opacity=0.5).encode(
x=alt.X("num_sched_timepoints:Q").title("Number of Timepoints"),
y=alt.Y("wait assessment:Q").title("Wait Assessment (%)"),
color=alt.condition(subway_line_select, alt.Color(field="line", scale=alt.Scale(domain=line_names, range=line_colors)).legend(None), alt.value("lightgray")),
opacity=alt.condition(subway_line_select, alt.value(0.5), alt.value(0.1)),
column=alt.Column("period:N", header=alt.Header(labelFontSize=15)).title(None),
tooltip=["line:N", "wait assessment"]
).transform_filter(
(alt.datum.line != "Systemwide") & (alt.datum.day_type==1)
).properties(
title=alt.Title(
"MTA: 2015-2019 'Wait assessment' vs. Total Timepoints, by Subway Line",
subtitle=["Weekdays"],
# subtitle="'Wait Assessment' refers to how 'regularly spaced' the trains are."
),
width=200,
height=150
)
wait_vs_timepoints.add_params(subway_line_select)
final_fig = (
chart | (longitudinal_mta & wait_vs_timepoints)
)
final_fig
final_fig.save("MTA_Wait_assessment.html")