Public
Edited
Jan 18, 2024
Insert cell
Insert cell
embed = require("vega-embed@5")
Insert cell
vegalite = require("@observablehq/vega-lite@0.1")
Insert cell
import { aq, op } from '@uwdata/arquero'
Insert cell
viewof trains = aq // viewof shows the table view, but assigns the table value
.fromCSV(await FileAttachment('small_trains.csv').text())
.view({ height: 240 })
Insert cell
viewof gares = aq // viewof shows the table view, but assigns the table value
.fromCSV(await FileAttachment('garesdep@1.csv').text())
.view({ height: 240 })
Insert cell
trainswithdate = trains.derive({date:d=>op.datetime(d.year, d.month-1)})
Insert cell
trainsByStationsAndDate = trainswithdate.groupby('date', 'departure_station', 'arrival_station',).rollup({total_num_trips: d => op.mean(d.total_num_trips), num_late_at_departure: d=>op.mean(d.num_late_at_departure),num_arriving_late: d=>op.mean(d.num_arriving_late)})
Insert cell
trainsByStations = trainsByStationsAndDate.groupby('departure_station', 'arrival_station').rollup({total_num_trips: d => op.sum(d.total_num_trips), num_late_at_departure: d=>op.sum(d.num_late_at_departure),num_arriving_late: d=>op.sum(d.num_arriving_late)}).derive({ratio_late_at_departure: d=>d.num_late_at_departure/d.total_num_trips})
Insert cell
Insert cell
embed({
$schema: "https://vega.github.io/schema/vega-lite/v5.json",
width: 500,
height: 300,
data: {url: "https://mjlobo.github.io/teaching/eivp/departements.json",
format: {type: "topojson", feature: "departements"} // we have to specify which objects we will display
},
projection: {type :"mercator" },
mark: {
type: "geoshape"
}
})
Insert cell
Insert cell
embed({
$schema: "https://vega.github.io/schema/vega-lite/v5.json",
width: 500,
height: 300,
data: {url: "https://mjlobo.github.io/teaching/eivp/departementswithid.json",
format: {type: "topojson", feature: "departements"}
},
projection: {type :"mercator" },
mark: {
type: "geoshape",
stroke: 'light-grey',
fill: 'grey',
strokeWidth: 0.5
}
})
Insert cell
Insert cell
garespardepartement = gares.groupby("CODE_DEP").count()
Insert cell
embed({
$schema: "https://vega.github.io/schema/vega-lite/v5.json",
width: 500,
height: 300,
data: {
url: "https://mjlobo.github.io/teaching/eivp/departementswithid.json",
format: {type: "topojson", feature: "departements"}
},
transform: [{
lookup: "id", //lookup is the key in the origin table, in this case the topojson
from: {
data: {
values: garespardepartement.objects() // the table where we will look for the data
},
key: "CODE_DEP", // the key in the secondary table
fields: ["count"] // the fields we will keep
},
default:'0' // the default value if there is no match. Even if we want a number, we have to write it as text between ''
}],
projection: {
type: "mercator"
},
mark: "geoshape",
encoding: {
color: {
field: "count",
type: "quantitative"
}
}
})
Insert cell
Insert cell
trainsByStations.view()
Insert cell
trainsByDepartureStations = trainsByStations.groupby("departure_station").rollup({total_num_trips: (d) => op.sum(d.total_num_trips),
num_late_at_departure: (d) => op.sum(d.num_late_at_departure),
num_arriving_late: (d) => op.sum(d.num_arriving_late),
})
Insert cell
trainsByDepartureStationWithDepartement = trainsByDepartureStations.join_left(gares, ["departure_station", "LIBELLE"])
Insert cell
trainsByDepartureStationWithDepartement.objects()
Insert cell
trainsByDepartureStationAndDepartement = trainsByDepartureStationWithDepartement.groupby("CODE_DEP").rollup({
total_num_trips: (d) => op.sum(d.total_num_trips),
num_late_at_departure: (d) => op.sum(d.num_late_at_departure),
num_arriving_late: (d) => op.sum(d.num_arriving_late),
}).derive({ratio_late_at_departure: (d)=>d.num_late_at_departure/d.total_num_trips})
Insert cell
embed({
$schema: "https://vega.github.io/schema/vega-lite/v5.json",
width: 500,
height: 300,
data: {
url: "https://mjlobo.github.io/teaching/eivp/departementswithid.json",
format: {type: "topojson", feature: "departements"}
},
transform: [{
lookup: "id", //lookup is the key in the origin table, in this case the topojson
from: {
data: {
values: trainsByDepartureStationAndDepartement.objects() // the table where we will look for the data
},
key: "CODE_DEP", // the key in the secondary table
fields: ["ratio_late_at_departure"] // the fields we will keep
},
default:'0' // the default value if there is no match. Even if we want a number, we have to write it as text between ''
}],
projection: {
type: "mercator"
},
mark: "geoshape",
encoding: {
color: {
field: "ratio_late_at_departure",
type: "quantitative"
}
}
})
Insert cell
Insert cell
embed({
$schema: "https://vega.github.io/schema/vega-lite/v3.json",
width: 400,
height: 300,
layer: [
{
data: {
url: "https://mjlobo.github.io/teaching/eivp/departements.json",
format: {
type: "topojson",
feature: "departements"
}
},
projection: {
type: "mercator"
},
mark: {
type: "geoshape",
fill: "lightgray",
stroke: "white"
}
},
{
data: {
values: gares.objects()
},
projection: {
type: "mercator"
},
mark: "circle",
encoding: {
longitude: {
field: "X_WGS84",
type: "quantitative"
},
latitude: {
field: "Y_WGS84",
type: "quantitative"
},
size: {"value": 20},
color: {"value": "steelblue"}
}
}
]
})
Insert cell
Insert cell
trainsByDepartureStation = trainsByStations
.groupby("departure_station")
.rollup({
total_num_trips: (d) => op.sum(d.total_num_trips),
num_late_at_departure: (d) => op.sum(d.num_late_at_departure),
num_arriving_late: (d) => op.sum(d.num_arriving_late),
});
Insert cell
trainsByDepartureWithCoords = trainsByDepartureStation
.join_left(gares, ["departure_station", "LIBELLE"])
.filter((d) => d.LIBELLE != null)
.reify();
Insert cell
trainsByDepartureWithCoords.objects()
Insert cell
embed({
$schema: "https://vega.github.io/schema/vega-lite/v3.json",
width: 400,
height: 300,
layer: [
{
data: {
url: "https://mjlobo.github.io/teaching/eivp/departements.json",
format: {
type: "topojson",
feature: "departements"
}
},
projection: {
type: "mercator"
},
mark: {
type: "geoshape",
fill: "lightgray",
stroke: "white"
}
},
{
data: {
values: trainsByDepartureWithCoords.objects()
},
projection: {
type: "mercator"
},
mark: "circle",
encoding: {
longitude: {
field: "X_WGS84",
type: "quantitative"
},
latitude: {
field: "Y_WGS84",
type: "quantitative"
},
size: {"field": "total_num_trips", type: "quantitative"},
color: {"value": "steelblue"}
}
}
]
})
Insert cell
trainsByArrivalStation = trainsByStations
.groupby("arrival_station")
.rollup({
total_num_trips: (d) => op.sum(d.total_num_trips),
num_late_at_departure: (d) => op.sum(d.num_late_at_departure),
num_arriving_late: (d) => op.sum(d.num_arriving_late),
});

Insert cell
trainsByArrivalWithCoords = trainsByArrivalStation
.join_left(gares, ["arrival_station", "LIBELLE"])
.filter((d) => d.LIBELLE != null)
.reify();
Insert cell
embed({
$schema: "https://vega.github.io/schema/vega-lite/v3.json",
width: 400,
height: 300,
layer: [
{
data: {
url: "https://mjlobo.github.io/teaching/eivp/departements.json",
format: {
type: "topojson",
feature: "departements"
}
},
projection: {
type: "mercator"
},
mark: {
type: "geoshape",
fill: "lightgray",
stroke: "white"
}
},
{
data: {
values: trainsByDepartureWithCoords.objects()
},
projection: {
type: "mercator"
},
mark: "circle",
encoding: {
longitude: {
field: "X_WGS84",
type: "quantitative"
},
latitude: {
field: "Y_WGS84",
type: "quantitative"
},
size: {"field": "total_num_trips", type: "quantitative"},
color: {"value": "steelblue"}
}
},
{
data: {
values: trainsByArrivalWithCoords.objects()
},
projection: {
type: "mercator"
},
mark: "circle",
encoding: {
longitude: {
field: "X_WGS84",
type: "quantitative"
},
latitude: {
field: "Y_WGS84",
type: "quantitative"
},
size: {"field": "total_num_trips", type: "quantitative"},
color: {"value": "orange"}
}
}
]
})
Insert cell
Insert cell
embed({
$schema: "https://vega.github.io/schema/vega-lite/v5.json",
width: 600,
height: 300,
layer: [
{
data: {
url: "https://mjlobo.github.io/teaching/eivp/departements.json",
format: {
type: "topojson",
feature: "departements"
}
},
projection: {
type: "mercator"
},
mark: {
type: "geoshape",
fill: "lightgray",
stroke: "white"
}
},
{
data: {
values: gares.objects()
},
projection: {
type: "mercator"
},
mark: {type: "point"},
encoding: {
longitude: {
field: "X_WGS84",
type: "quantitative"
},
latitude: {
field: "Y_WGS84",
type: "quantitative"
},
size: {"value": 20},
color: {"value": "steelblue"}
}
},
{ //we add a layer to represent the trips
data: {
values: trainsByStations.objects()
},
transform: [
{
lookup: "departure_station",
from: {
data: {values: gares.objects()},
key: "LIBELLE",
fields: ["X_WGS84", "Y_WGS84"],
},
as: ["X_WGS84_departure", "Y_WGS84_departure"]
},
{
lookup: "arrival_station",
from: {
data: {values: gares.objects()},
key: "LIBELLE",
fields: ["X_WGS84", "Y_WGS84"],
},
as: ["X_WGS84_arrival", "Y_WGS84_arrival"]
},
{filter: "datum.X_WGS84_departure != null && datum.Y_WGS84_departure != null && datum.X_WGS84_arrival!= null && datum.Y_WGS84_arrival != null"} // we add a filter to only consider trips coming and going to stations that have valid coordinates
],
projection: {
type: "mercator"
},
mark: "rule",
encoding: {
longitude: {
field: "X_WGS84_departure",
type: "quantitative"
},
latitude: {
field: "Y_WGS84_departure",
type: "quantitative"
},
longitude2: {
field: "X_WGS84_arrival",
type: "quantitative"
},
latitude2: {
field: "Y_WGS84_arrival",
type: "quantitative"
},
color: {"value": "steelblue"}
}
}
]
})
Insert cell
md` ### Exercise
1. Represent the number of trips using the color of the lines.
2. Represent the number of trips using the width of the lines.
3. Represent the number of trips using the opacity of lines.
4. Discuss the different advantages and incovenients of these visualizations.`
Insert cell
maxNumTrains = trainsByStations.rollup({ maxNumTrains: d => op.max(d.total_num_trips) }).objects()[0].maxNumTrains
Insert cell
embed({
$schema: "https://vega.github.io/schema/vega-lite/v3.json",
width: 400,
height: 300,
layer: [
{
data: {
url: "https://mjlobo.github.io/teaching/eivp/departements.json",
format: {
type: "topojson",
feature: "departements",
},
},
projection: {
type: "mercator",
},
mark: {
type: "geoshape",
fill: "lightgray",
stroke: "white",
},
},
{
data: {
values: gares.objects(),
},
projection: {
type: "mercator",
},
mark: { type: "point" },
encoding: {
longitude: {
field: "X_WGS84",
type: "quantitative",
},
latitude: {
field: "Y_WGS84",
type: "quantitative",
},
size: { value: 20 },
color: { value: "steelblue" },
},
},
{
//we add a layer to represent the trips
data: {
values: trainsByStations.objects(),
},
transform: [
{
lookup: "departure_station",
from: {
data: { values: gares.objects() },
key: "LIBELLE",
fields: ["X_WGS84", "Y_WGS84"],
},
as: ["X_WGS84_departure", "Y_WGS84_departure"],
},
{
lookup: "arrival_station",
from: {
data: { values: gares.objects() },
key: "LIBELLE",
fields: ["X_WGS84", "Y_WGS84"],
},
as: ["X_WGS84_arrival", "Y_WGS84_arrival"],
},
{
filter:
"datum.X_WGS84_departure != null && datum.Y_WGS84_departure != null && datum.X_WGS84_arrival!= null && datum.Y_WGS84_arrival != null",
}, // we add a filter to only consider trips coming and going to stations that have valid coordinates
],
projection: {
type: "mercator",
},
mark: "rule",
encoding: {
longitude: {
field: "X_WGS84_departure",
type: "quantitative",
},
latitude: {
field: "Y_WGS84_departure",
type: "quantitative",
},
longitude2: {
field: "X_WGS84_arrival",
type: "quantitative",
},
latitude2: {
field: "Y_WGS84_arrival",
type: "quantitative",
},
color: { field: "total_num_trips", type: "quantitative" },
},
},
],
})
Insert cell
embed({
$schema: "https://vega.github.io/schema/vega-lite/v3.json",
width: 400,
height: 300,
layer: [
{
data: {
url: "https://mjlobo.github.io/teaching/eivp/departements.json",
format: {
type: "topojson",
feature: "departements",
},
},
projection: {
type: "mercator",
},
mark: {
type: "geoshape",
fill: "lightgray",
stroke: "white",
},
},
{
data: {
values: gares.objects(),
},
projection: {
type: "mercator",
},
mark: { type: "point" },
encoding: {
longitude: {
field: "X_WGS84",
type: "quantitative",
},
latitude: {
field: "Y_WGS84",
type: "quantitative",
},
size: { value: 20 },
color: { value: "steelblue" },
},
},
{
//we add a layer to represent the trips
data: {
values: trainsByStations.objects(),
},
transform: [
{
lookup: "departure_station",
from: {
data: { values: gares.objects() },
key: "LIBELLE",
fields: ["X_WGS84", "Y_WGS84"],
},
as: ["X_WGS84_departure", "Y_WGS84_departure"],
},
{
lookup: "arrival_station",
from: {
data: { values: gares.objects() },
key: "LIBELLE",
fields: ["X_WGS84", "Y_WGS84"],
},
as: ["X_WGS84_arrival", "Y_WGS84_arrival"],
},
{
filter:
"datum.X_WGS84_departure != null && datum.Y_WGS84_departure != null && datum.X_WGS84_arrival!= null && datum.Y_WGS84_arrival != null",
}, // we add a filter to only consider trips coming and going to stations that have valid coordinates
],
projection: {
type: "mercator",
},
mark: "rule",
encoding: {
longitude: {
field: "X_WGS84_departure",
type: "quantitative",
},
latitude: {
field: "Y_WGS84_departure",
type: "quantitative",
},
longitude2: {
field: "X_WGS84_arrival",
type: "quantitative",
},
latitude2: {
field: "Y_WGS84_arrival",
type: "quantitative",
},
color: { value: "steelblue" },
strokeWidth: {
field: "total_num_trips",
type: "quantitative",
},
},
},
],
})
Insert cell
embed({
$schema: "https://vega.github.io/schema/vega-lite/v3.json",
width: 400,
height: 300,
layer: [
{
data: {
url: "https://mjlobo.github.io/teaching/eivp/departements.json",
format: {
type: "topojson",
feature: "departements",
},
},
projection: {
type: "mercator",
},
mark: {
type: "geoshape",
fill: "lightgray",
stroke: "white",
},
},
{
data: {
values: gares.objects(),
},
projection: {
type: "mercator",
},
mark: { type: "point" },
encoding: {
longitude: {
field: "X_WGS84",
type: "quantitative",
},
latitude: {
field: "Y_WGS84",
type: "quantitative",
},
size: { value: 20 },
color: { value: "steelblue" },
},
},
{
//we add a layer to represent the trips
data: {
values: trainsByStations.objects(),
},
transform: [
{
lookup: "departure_station",
from: {
data: { values: gares.objects() },
key: "LIBELLE",
fields: ["X_WGS84", "Y_WGS84"],
},
as: ["X_WGS84_departure", "Y_WGS84_departure"],
},
{
lookup: "arrival_station",
from: {
data: { values: gares.objects() },
key: "LIBELLE",
fields: ["X_WGS84", "Y_WGS84"],
},
as: ["X_WGS84_arrival", "Y_WGS84_arrival"],
},
{
filter:
"datum.X_WGS84_departure != null && datum.Y_WGS84_departure != null && datum.X_WGS84_arrival!= null && datum.Y_WGS84_arrival != null",
}, // we add a filter to only consider trips coming and going to stations that have valid coordinates
],
projection: {
type: "mercator",
},
mark: "rule",
encoding: {
longitude: {
field: "X_WGS84_departure",
type: "quantitative",
},
latitude: {
field: "Y_WGS84_departure",
type: "quantitative",
},
longitude2: {
field: "X_WGS84_arrival",
type: "quantitative",
},
latitude2: {
field: "Y_WGS84_arrival",
type: "quantitative",
},
color: { value: "steelblue" },
opacity: {
field: "total_num_trips",
type: "quantitative",
},
},
},
],
})
Insert cell
Insert cell
trainsByDepartureWithCoordsForJoin = trainsByDepartureWithCoords.select({
departure_station: "departure_station",
total_num_trips: "total_num_trips_departure",
});

Insert cell
trainsByArrivalWithCoordsForJoin = trainsByArrivalWithCoords.select({
arrival_station: "arrival_station",
total_num_trips: "total_num_trips_arrival",
X_WGS84: "X_WGS84",
Y_WGS84: "Y_WGS84",
});
Insert cell
trainsWithArrivalAndDeparture =
trainsByDepartureWithCoordsForJoin.join_left(
trainsByArrivalWithCoordsForJoin,
["departure_station", "arrival_station"]
);
Insert cell
trainsWithDiff = trainsWithArrivalAndDeparture.derive({
diff_arrival_departure: (d) =>
d.total_num_trips_arrival - d.total_num_trips_departure,
});

Insert cell
embed({
$schema: "https://vega.github.io/schema/vega-lite/v5.json",
width: 500,
height: 300,
layer: [
{
data: {
url: "https://mjlobo.github.io/teaching/eivp/departements.json",
format: {
type: "topojson",
feature: "departements",
},
},
projection: {
type: "mercator",
},
mark: {
type: "geoshape",
fill: "lightgray",
stroke: "white",
},
},
{
data: {
values: trainsWithDiff.objects(),
},
projection: {
type: "mercator",
},
mark: { type: "circle" },
encoding: {
longitude: {
field: "X_WGS84",
type: "quantitative",
},
latitude: {
field: "Y_WGS84",
type: "quantitative",
},
size: {
field: "diff_arrival_departure",
type: "quantitative",
},
color: {
value: "steelblue",
},
},
},
],
})
Insert cell
embed({
$schema: "https://vega.github.io/schema/vega-lite/v5.json",
width: 500,
height: 300,
layer: [
{
data: {
url: "https://mjlobo.github.io/teaching/eivp/departements.json",
format: {
type: "topojson",
feature: "departements",
},
},
projection: {
type: "mercator",
},
mark: {
type: "geoshape",
fill: "lightgray",
stroke: "white",
},
},
{
data: {
values: trainsWithDiff.objects(),
},
projection: {
type: "mercator",
},
mark: { type: "circle" },
encoding: {
longitude: {
field: "X_WGS84",
type: "quantitative",
},
latitude: {
field: "Y_WGS84",
type: "quantitative",
},
size: {
value: 100,
},
color: {
field: "diff_arrival_departure",
type: "quantitative",
scale: { scheme: "blueorange" },
},
},
},
],
})
Insert cell
trainsWithDiffBoolean = trainsWithDiff.derive({
is_arrival_bigger: (d) => (d.diff_arrival_departure > 0 ? "true" : "false"),
});
Insert cell
embed({
$schema: "https://vega.github.io/schema/vega-lite/v5.json",
width: 500,
height: 300,
layer: [
{
data: {
url: "https://mjlobo.github.io/teaching/eivp/departements.json",
format: {
type: "topojson",
feature: "departements",
},
},
projection: {
type: "mercator",
},
mark: {
type: "geoshape",
fill: "lightgray",
stroke: "white",
},
},
{
data: {
values: trainsWithDiffBoolean.objects(),
},
projection: {
type: "mercator",
},
mark: { type: "circle" },
encoding: {
longitude: {
field: "X_WGS84",
type: "quantitative",
},
latitude: {
field: "Y_WGS84",
type: "quantitative",
},
size: {
value: 100,
},
color: {
field: "is_arrival_bigger",
},
},
},
],
})
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more