From 5fa3cb28227eb8d420a1e1af6719ec293654153c Mon Sep 17 00:00:00 2001 From: rakow Date: Sat, 16 Mar 2024 12:00:10 +0100 Subject: [PATCH 01/31] trip analysis for subgroups --- .../analysis/population/TripAnalysis.java | 19 +- .../population/TripByGroupAnalysis.java | 256 ++++++++++++++++++ 2 files changed, 271 insertions(+), 4 deletions(-) create mode 100644 contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java index bc656e79a2f..997869cae1d 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java @@ -27,12 +27,11 @@ import tech.tablesaw.joining.DataFrameJoiner; import tech.tablesaw.selection.Selection; -import java.io.*; +import java.io.IOException; import java.math.BigDecimal; import java.math.RoundingMode; import java.nio.file.Files; import java.util.*; -import java.util.zip.GZIPInputStream; import static tech.tablesaw.aggregate.AggregateFunctions.count; @@ -50,6 +49,9 @@ public class TripAnalysis implements MATSimAppCommand { @CommandLine.Mixin private OutputOptions output = OutputOptions.ofCommand(TripAnalysis.class); + @CommandLine.Option(names = "--input-ref-data", description = "Optional path to reference data", required = false) + private String refData; + @CommandLine.Option(names = "--match-id", description = "Pattern to filter agents by id") private String matchId; @@ -156,6 +158,12 @@ public Integer call() throws Exception { trips = trips.where(Selection.with(idx.toIntArray())); } + TripByGroupAnalysis groups = null; + if (refData != null) { + groups = new TripByGroupAnalysis(refData); + groups.groupPersons(persons); + } + // Use longest_distance_mode where main_mode is not present trips.stringColumn("main_mode") .set(trips.stringColumn("main_mode").isMissing(), @@ -180,6 +188,10 @@ public Integer call() throws Exception { writeModeShare(joined, labels); + if (groups != null) { + groups.analyzeModeShare(joined, labels); + } + writePopulationStats(persons, joined); writeTripStats(joined); @@ -370,8 +382,7 @@ private void writeTripPurposes(Table trips) { TextColumn purpose = trips.textColumn("end_activity_type"); // Remove suffix durations like _345 - Selection withDuration = purpose.matchesRegex("^.+_[0-9]+$"); - purpose.set(withDuration, purpose.where(withDuration).replaceAll("_[0-9]+$", "")); + purpose.set(Selection.withRange(0, purpose.size()), purpose.replaceAll("_[0-9]{2,}$", "")); Table tArrival = trips.summarize("trip_id", count).by("end_activity_type", "arrival_h"); diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java new file mode 100644 index 00000000000..4b0be56963b --- /dev/null +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java @@ -0,0 +1,256 @@ +package org.matsim.application.analysis.population; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.core.utils.io.IOUtils; +import tech.tablesaw.api.*; +import tech.tablesaw.columns.Column; +import tech.tablesaw.io.csv.CsvReadOptions; +import tech.tablesaw.joining.DataFrameJoiner; +import tech.tablesaw.selection.Selection; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.*; + +import static tech.tablesaw.aggregate.AggregateFunctions.count; + +/** + * Helper class to analyze trips by groups. + * This class can not be used on its own, but will be called by {@link TripAnalysis}. + */ +final class TripByGroupAnalysis { + + private final static Logger log = LogManager.getLogger(TripByGroupAnalysis.class); + + /** + * Contains detected groups and their reference data. + */ + private final List groups; + + private final Map categories; + + TripByGroupAnalysis(String refData) throws IOException { + + try (BufferedReader reader = IOUtils.getBufferedReader(refData)) { + Table ref = Table.read().csv(CsvReadOptions.builder(reader) + .columnTypes((column) -> column.equals("share") ? ColumnType.DOUBLE : ColumnType.STRING) + .sample(false) + .build()); + + List columns = new ArrayList<>(ref.columnNames()); + // remove non group columns + columns.removeAll(Set.of("dist_group", "main_mode", "share")); + + // Collect all contained groups + Set> groups = new HashSet<>(); + for (Row row : ref) { + + List g = new ArrayList<>(); + for (String c : columns) { + if (!row.getString(c).isEmpty()) + g.add(c); + } + if (!g.isEmpty()) + groups.add(g); + } + + log.info("Detect groups: {}", groups); + + this.groups = new ArrayList<>(); + + for (List g : groups) { + + Selection sel = Selection.withRange(0, ref.rowCount()); + for (String c : g) { + sel = sel.and(ref.stringColumn(c).isNotEqualTo("")); + } + + Table gRef = ref.where(sel); + this.groups.add(new Group(g, gRef)); + } + + this.categories = new HashMap<>(); + for (List group : groups) { + for (String g : group) { + if (!this.categories.containsKey(g)) { + this.categories.put(g, new Category(ref.column(g))); + } + } + } + + } + } + + void analyzeModeShare(Table trips, List dists) { + + for (Group group : groups) { + + List columns = new ArrayList<>(List.of("dist_group", "main_mode")); + columns.addAll(group.columns); + + String[] join = columns.toArray(new String[0]); + + Table aggr = trips.summarize("trip_id", count).by(join); + + int idx = aggr.columnCount() - 1; + DoubleColumn share = aggr.numberColumn(idx).divide(aggr.numberColumn(idx).sum()).setName("sim_share"); + aggr.replaceColumn(idx, share); + + // Sort by dist_group and mode + Comparator cmp = Comparator.comparingInt(row -> dists.indexOf(row.getString("dist_group"))); + aggr = aggr.sortOn(cmp.thenComparing(row -> row.getString("main_mode"))); + + // TODO: norm by category and dist_group + // probably need two separate files as well (with and without dist) + // not normed is more useful for now + + Table joined = new DataFrameJoiner(group.data, join).leftOuter(aggr); + joined.column("share").setName("ref_share"); + + joined.removeColumns( + joined.columnNames().stream() + .filter(c -> !columns.contains(c) && !c.equals("sim_share") && !c.equals("ref_share")) + .toArray(String[]::new) + ); + + // TODO: write trip analysis, obtain output path from TripAnalysis +// aggr.write().csv(output.getPath("mode_share_per_dist.csv").toFile()); + + } + } + + void groupPersons(Table persons) { + + for (Group g : groups) { + for (String c : g.columns) { + + if (!persons.columnNames().contains(c)) { + log.error("Column {} not found in persons table", c); + persons.addColumns(StringColumn.create(c, persons.rowCount())); + continue; + } + + Column column = persons.column(c); + + StringColumn copy = column.emptyCopy(column.size()).asStringColumn().setName(c); + column.mapInto((Object value) -> categories.get(c).categorize(value), copy); + persons.replaceColumn(c, copy); + } + } + } + + private record Group(List columns, Table data) { + } + + private static final class Category { + + /** + * Unique values of the category. + */ + private final Set values; + + /** + * Groups of values that have been subsumed under a single category. + * These are values separated by , + */ + private final Map grouped; + + /** + * Range categories. + */ + private final List ranges; + + public Category(Column data) { + this.values = data.asStringColumn().unique() + .removeMissing() + .asSet(); + + this.grouped = new HashMap<>(); + for (String v : values) { + if (v.contains(",")) { + String[] grouped = v.split(","); + for (String g : grouped) { + this.grouped.put(g, v); + } + } + } + + boolean range = this.values.stream().allMatch(v -> v.contains("-") || v.contains("+")); + if (range) { + ranges = new ArrayList<>(); + for (String value : this.values) { + if (value.contains("-")) { + String[] parts = value.split("-"); + ranges.add(new Range(Double.parseDouble(parts[0]), Double.parseDouble(parts[1]), value)); + } else if (value.contains("+")) { + ranges.add(new Range(Double.parseDouble(value.replace("+", "")), Double.POSITIVE_INFINITY, value)); + } + } + + ranges.sort(Comparator.comparingDouble(r -> r.left)); + } else + ranges = null; + } + + /** + * Categorize a single value. + */ + public String categorize(Object value) { + + if (value == null) + return null; + + // TODO: handle booleans + + if (value instanceof Number) { + return categorizeNumber((Number) value); + } else { + String v = value.toString(); + if (values.contains(v)) + return v; + else if (grouped.containsKey(v)) + return grouped.get(v); + + try { + double d = Double.parseDouble(v); + return categorizeNumber(d); + } catch (NumberFormatException e) { + return null; + } + } + } + + private String categorizeNumber(Number value) { + + if (ranges != null) { + for (Range r : ranges) { + if (value.doubleValue() >= r.left && value.doubleValue() < r.right) + return r.label; + } + } + + // Match string representation + // TODO: int and float could be represented differently + String v = value.toString(); + if (values.contains(v)) + return v; + else if (grouped.containsKey(v)) + return grouped.get(v); + + return null; + } + + } + + /** + * @param left Left bound of the range. + * @param right Right bound of the range. (exclusive) + * @param label Label of this group. + */ + private record Range(double left, double right, String label) { + + + } + +} From cdf15b30f478feb7ee42748b121300a3c947bd23 Mon Sep 17 00:00:00 2001 From: rakow Date: Fri, 22 Mar 2024 19:34:29 +0100 Subject: [PATCH 02/31] refactor categories --- .../analysis/population/Category.java | 133 ++++++++++++++++++ .../population/TripByGroupAnalysis.java | 112 +-------------- .../analysis/population/CategoryTest.java | 65 +++++++++ 3 files changed, 199 insertions(+), 111 deletions(-) create mode 100644 contribs/application/src/main/java/org/matsim/application/analysis/population/Category.java create mode 100644 contribs/application/src/test/java/org/matsim/application/analysis/population/CategoryTest.java diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/Category.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/Category.java new file mode 100644 index 00000000000..bddf50a0cb4 --- /dev/null +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/Category.java @@ -0,0 +1,133 @@ +package org.matsim.application.analysis.population; + +import java.util.*; + +/** + * Categorize values into groups. + */ +public final class Category { + + private static final Set TRUE = Set.of("true", "yes", "1", "on", "y", "j", "ja"); + private static final Set FALSE = Set.of("false", "no", "0", "off", "n", "nein"); + + /** + * Unique values of the category. + */ + private final Set values; + + /** + * Groups of values that have been subsumed under a single category. + * These are values separated by , + */ + private final Map grouped; + + /** + * Range categories. + */ + private final List ranges; + + public Category(Set values) { + this.values = values; + this.grouped = new HashMap<>(); + for (String v : values) { + if (v.contains(",")) { + String[] grouped = v.split(","); + for (String g : grouped) { + this.grouped.put(g, v); + } + } + } + + boolean range = this.values.stream().allMatch(v -> v.contains("-") || v.contains("+")); + if (range) { + ranges = new ArrayList<>(); + for (String value : this.values) { + if (value.contains("-")) { + String[] parts = value.split("-"); + ranges.add(new Range(Double.parseDouble(parts[0]), Double.parseDouble(parts[1]), value)); + } else if (value.contains("+")) { + ranges.add(new Range(Double.parseDouble(value.replace("+", "")), Double.POSITIVE_INFINITY, value)); + } + } + + ranges.sort(Comparator.comparingDouble(r -> r.left)); + } else + ranges = null; + + + // Check if all values are boolean + if (values.stream().allMatch(v -> TRUE.contains(v.toLowerCase()) || FALSE.contains(v.toLowerCase()))) { + for (String value : values) { + Set group = TRUE.contains(value.toLowerCase()) ? TRUE : FALSE; + for (String g : group) { + this.grouped.put(g, value); + } + } + } + } + + /** + * Categorize a single value. + */ + public String categorize(Object value) { + + if (value == null) + return null; + + if (value instanceof Boolean) { + // Booleans and synonyms are in the group map + return categorize(((Boolean) value).toString().toLowerCase()); + } else if (value instanceof Number) { + return categorizeNumber((Number) value); + } else { + String v = value.toString(); + if (values.contains(v)) + return v; + else if (grouped.containsKey(v)) + return grouped.get(v); + + try { + double d = Double.parseDouble(v); + return categorizeNumber(d); + } catch (NumberFormatException e) { + return null; + } + } + } + + private String categorizeNumber(Number value) { + + if (ranges != null) { + for (Range r : ranges) { + if (value.doubleValue() >= r.left && value.doubleValue() < r.right) + return r.label; + } + } + + // Match string representation + String v = value.toString(); + if (values.contains(v)) + return v; + else if (grouped.containsKey(v)) + return grouped.get(v); + + + // Convert the number to a whole number, which will have a different string representation + if (value instanceof Float || value instanceof Double) { + return categorizeNumber(value.longValue()); + } + + return null; + } + + /** + * @param left Left bound of the range. + * @param right Right bound of the range. (exclusive) + * @param label Label of this group. + */ + private record Range(double left, double right, String label) { + + + } + +} diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java index 4b0be56963b..91e80dbaca2 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java @@ -74,7 +74,7 @@ final class TripByGroupAnalysis { for (List group : groups) { for (String g : group) { if (!this.categories.containsKey(g)) { - this.categories.put(g, new Category(ref.column(g))); + this.categories.put(g, new Category(ref.column(g).asStringColumn().removeMissing().asSet())); } } } @@ -143,114 +143,4 @@ void groupPersons(Table persons) { private record Group(List columns, Table data) { } - private static final class Category { - - /** - * Unique values of the category. - */ - private final Set values; - - /** - * Groups of values that have been subsumed under a single category. - * These are values separated by , - */ - private final Map grouped; - - /** - * Range categories. - */ - private final List ranges; - - public Category(Column data) { - this.values = data.asStringColumn().unique() - .removeMissing() - .asSet(); - - this.grouped = new HashMap<>(); - for (String v : values) { - if (v.contains(",")) { - String[] grouped = v.split(","); - for (String g : grouped) { - this.grouped.put(g, v); - } - } - } - - boolean range = this.values.stream().allMatch(v -> v.contains("-") || v.contains("+")); - if (range) { - ranges = new ArrayList<>(); - for (String value : this.values) { - if (value.contains("-")) { - String[] parts = value.split("-"); - ranges.add(new Range(Double.parseDouble(parts[0]), Double.parseDouble(parts[1]), value)); - } else if (value.contains("+")) { - ranges.add(new Range(Double.parseDouble(value.replace("+", "")), Double.POSITIVE_INFINITY, value)); - } - } - - ranges.sort(Comparator.comparingDouble(r -> r.left)); - } else - ranges = null; - } - - /** - * Categorize a single value. - */ - public String categorize(Object value) { - - if (value == null) - return null; - - // TODO: handle booleans - - if (value instanceof Number) { - return categorizeNumber((Number) value); - } else { - String v = value.toString(); - if (values.contains(v)) - return v; - else if (grouped.containsKey(v)) - return grouped.get(v); - - try { - double d = Double.parseDouble(v); - return categorizeNumber(d); - } catch (NumberFormatException e) { - return null; - } - } - } - - private String categorizeNumber(Number value) { - - if (ranges != null) { - for (Range r : ranges) { - if (value.doubleValue() >= r.left && value.doubleValue() < r.right) - return r.label; - } - } - - // Match string representation - // TODO: int and float could be represented differently - String v = value.toString(); - if (values.contains(v)) - return v; - else if (grouped.containsKey(v)) - return grouped.get(v); - - return null; - } - - } - - /** - * @param left Left bound of the range. - * @param right Right bound of the range. (exclusive) - * @param label Label of this group. - */ - private record Range(double left, double right, String label) { - - - } - } diff --git a/contribs/application/src/test/java/org/matsim/application/analysis/population/CategoryTest.java b/contribs/application/src/test/java/org/matsim/application/analysis/population/CategoryTest.java new file mode 100644 index 00000000000..7395864e575 --- /dev/null +++ b/contribs/application/src/test/java/org/matsim/application/analysis/population/CategoryTest.java @@ -0,0 +1,65 @@ +package org.matsim.application.analysis.population; + +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +class CategoryTest { + + @Test + void standard() { + + Category c = new Category(Set.of("a", "b", "c")); + + assertThat(c.categorize("a")).isEqualTo("a"); + assertThat(c.categorize("b")).isEqualTo("b"); + assertThat(c.categorize("c")).isEqualTo("c"); + assertThat(c.categorize("d")).isNull(); + + } + + @Test + void ranges() { + + Category c = new Category(Set.of("1-2", "2-4", "4+")); + + assertThat(c.categorize("1")).isEqualTo("1-2"); + assertThat(c.categorize(1)).isEqualTo("1-2"); + assertThat(c.categorize(1.0)).isEqualTo("1-2"); + + assertThat(c.categorize("2")).isEqualTo("2-4"); + assertThat(c.categorize("3")).isEqualTo("2-4"); + assertThat(c.categorize("5")).isEqualTo("4+"); + assertThat(c.categorize(5)).isEqualTo("4+"); + assertThat(c.categorize(5.0)).isEqualTo("4+"); + + } + + @Test + void grouped() { + + Category c = new Category(Set.of("a,b", "101,102")); + + assertThat(c.categorize("a")).isEqualTo("a,b"); + assertThat(c.categorize("b")).isEqualTo("a,b"); + assertThat(c.categorize(101)).isEqualTo("101,102"); + assertThat(c.categorize(102)).isEqualTo("101,102"); + + } + + @Test + void bool() { + + Category c = new Category(Set.of("y", "n")); + + assertThat(c.categorize("y")).isEqualTo("y"); + assertThat(c.categorize("yes")).isEqualTo("y"); + assertThat(c.categorize("1")).isEqualTo("y"); + + assertThat(c.categorize(true)).isEqualTo("y"); + assertThat(c.categorize(false)).isEqualTo("n"); + + } +} From ffa2f7f8daeefb5ef0c3d159201b79b9d3890cc4 Mon Sep 17 00:00:00 2001 From: rakow Date: Mon, 25 Mar 2024 17:04:16 +0100 Subject: [PATCH 03/31] write trip analysis --- .../analysis/population/TripAnalysis.java | 5 ++-- .../population/TripByGroupAnalysis.java | 9 +++---- .../simwrapper/dashboard/TripDashboard.java | 24 +++++++++++++++++++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java index 997869cae1d..e7f7879587e 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java @@ -38,7 +38,8 @@ @CommandLine.Command(name = "trips", description = "Calculates various trip related metrics.") @CommandSpec( requires = {"trips.csv", "persons.csv"}, - produces = {"mode_share.csv", "mode_share_per_dist.csv", "mode_users.csv", "trip_stats.csv", "population_trip_stats.csv", "trip_purposes_by_hour.csv"} + produces = {"mode_share.csv", "mode_share_per_dist.csv", "mode_users.csv", "trip_stats.csv", + "mode_share_per_%s.csv", "population_trip_stats.csv", "trip_purposes_by_hour.csv"} ) public class TripAnalysis implements MATSimAppCommand { @@ -189,7 +190,7 @@ public Integer call() throws Exception { writeModeShare(joined, labels); if (groups != null) { - groups.analyzeModeShare(joined, labels); + groups.analyzeModeShare(joined, labels, (g) -> output.getPath("mode_share_per_%s.csv", g)); } writePopulationStats(persons, joined); diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java index 91e80dbaca2..b4073266def 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java @@ -11,7 +11,9 @@ import java.io.BufferedReader; import java.io.IOException; +import java.nio.file.Path; import java.util.*; +import java.util.function.Function; import static tech.tablesaw.aggregate.AggregateFunctions.count; @@ -82,7 +84,7 @@ final class TripByGroupAnalysis { } } - void analyzeModeShare(Table trips, List dists) { + void analyzeModeShare(Table trips, List dists, Function output) { for (Group group : groups) { @@ -114,9 +116,8 @@ void analyzeModeShare(Table trips, List dists) { .toArray(String[]::new) ); - // TODO: write trip analysis, obtain output path from TripAnalysis -// aggr.write().csv(output.getPath("mode_share_per_dist.csv").toFile()); - + String name = String.join("_", group.columns); + aggr.write().csv(output.apply(name).toFile()); } } diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index 30c4a140e6b..ee4e1812106 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -26,6 +26,8 @@ public class TripDashboard implements Dashboard { @Nullable private final String modeUsersRefCsv; + private String groupedRefData; + private String[] args; /** @@ -51,6 +53,14 @@ public TripDashboard(@Nullable String modeShareRefCsv, @Nullable String modeShar args = new String[0]; } + /** + * Set grouped reference data. Will enable additional tab with analysis for subgroups of the population. + */ + public TripDashboard withGroupedRefData(String groupedRefData) { + this.groupedRefData = groupedRefData; + return this; + } + /** * Set argument that will be passed to the analysis script. See {@link TripAnalysis}. */ @@ -65,6 +75,15 @@ public void configure(Header header, Layout layout) { header.title = "Trips"; header.description = "General information about modal share and trip distributions."; + String[] args = new String[this.groupedRefData == null ? this.args.length: this.args.length + 2]; + System.arraycopy(this.args, 0, args, 0, this.args.length); + + // Add ref data to the argument if set + if (groupedRefData != null) { + args[this.args.length] = "--input-ref-data"; + args[this.args.length + 1] = groupedRefData; + } + Layout.Row first = layout.row("first"); first.el(Plotly.class, (viz, data) -> { viz.title = "Modal split"; @@ -226,5 +245,10 @@ public void configure(Header header, Layout layout) { }); + if (groupedRefData != null) { + + // TODO create the additional tab + + } } } From 50c833eda70409b1b3d180744d9c797f23de6132 Mon Sep 17 00:00:00 2001 From: rakow Date: Tue, 26 Mar 2024 09:59:03 +0100 Subject: [PATCH 04/31] write out correct table --- .../application/analysis/population/TripByGroupAnalysis.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java index b4073266def..62d95d0a152 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java @@ -117,7 +117,7 @@ void analyzeModeShare(Table trips, List dists, Function ou ); String name = String.join("_", group.columns); - aggr.write().csv(output.apply(name).toFile()); + joined.write().csv(output.apply(name).toFile()); } } From 1b45ce1a847039b683e6e346a033d8750627b578 Mon Sep 17 00:00:00 2001 From: rakow Date: Tue, 9 Apr 2024 09:35:31 +0200 Subject: [PATCH 05/31] use static method --- .../matsim/application/analysis/population/TripAnalysis.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java index 1d2579355ef..f91d7598ff4 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java @@ -98,7 +98,7 @@ public Integer call() throws Exception { Table persons = Table.read().csv(CsvReadOptions.builder(IOUtils.getBufferedReader(input.getPath("persons.csv"))) .columnTypesPartial(Map.of("person", ColumnType.TEXT)) .sample(false) - .separator(new CsvOptions().detectDelimiter(input.getPath("persons.csv"))).build()); + .separator(CsvOptions.detectDelimiter(input.getPath("persons.csv"))).build()); int total = persons.rowCount(); From c50c900700a5e5a4e06404953751a0ce1f216cc5 Mon Sep 17 00:00:00 2001 From: frievoe97 Date: Tue, 9 Apr 2024 15:10:41 +0200 Subject: [PATCH 06/31] prepare for facets, add TODOs --- .../simwrapper/dashboard/TripDashboard.java | 21 ++++++++++++++++++- .../org/matsim/simwrapper/viz/Plotly.java | 6 ++++++ .../simwrapper/dashboard/DashboardTests.java | 6 +++++- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index ee4e1812106..3f15a067e8a 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -75,7 +75,7 @@ public void configure(Header header, Layout layout) { header.title = "Trips"; header.description = "General information about modal share and trip distributions."; - String[] args = new String[this.groupedRefData == null ? this.args.length: this.args.length + 2]; + String[] args = new String[this.groupedRefData == null ? this.args.length : this.args.length + 2]; System.arraycopy(this.args, 0, args, 0, this.args.length); // Add ref data to the argument if set @@ -247,6 +247,25 @@ public void configure(Header header, Layout layout) { if (groupedRefData != null) { + layout.row("arrivals").el(Plotly.class, (viz, data) -> { + + viz.title = "FACETS"; + viz.description = "by hour and purpose"; + viz.layout = tech.tablesaw.plotly.components.Layout.builder() + .xAxis(Axis.builder().title("Hour").build()) + .yAxis(Axis.builder().title("Share").build()) + .barMode(tech.tablesaw.plotly.components.Layout.BarMode.STACK) + .build(); + + viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT).build(), + viz.addDataset(data.compute(TripAnalysis.class, "trip_purposes_by_hour.csv")).mapping() + .name("purpose", ColorScheme.Spectral) + .x("h") + .y("arrival") + ); + + }); + // TODO create the additional tab } diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Plotly.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Plotly.java index 4514035af4f..6006a297e98 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Plotly.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Plotly.java @@ -323,6 +323,8 @@ enum ColumnType { SIZE, COLOR, OPACITY + + // TODO: facet col und row } /** @@ -365,6 +367,8 @@ private Object toJSON() { aggregate == null) return file; + // TODO: normalize und rename optionen + return this; } @@ -507,6 +511,8 @@ public DataMapping opacity(String columnName) { return this; } + // TODO: facet col und row im trace + private void insert(Map obj) { // None standard attribute name to preserve its meaning diff --git a/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java b/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java index f35fdb8b5ea..cd5b0b2a9f5 100644 --- a/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java +++ b/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java @@ -77,7 +77,11 @@ void tripRef() { Path out = Path.of(utils.getOutputDirectory(), "analysis", "population"); - run(new TripDashboard("mode_share_ref.csv", "mode_share_per_dist_ref.csv", "mode_users_ref.csv")); + TripDashboard dashboard = new TripDashboard("mode_share_ref.csv", "mode_share_per_dist_ref.csv", "mode_users_ref.csv"); + + dashboard.withGroupedRefData("grouped_ref.csv"); + + run(dashboard); Assertions.assertThat(out) .isDirectoryContaining("glob:**trip_stats.csv") .isDirectoryContaining("glob:**mode_share.csv"); From 4b5157351aa3b8477750ec12ca1209a82e9ed0cc Mon Sep 17 00:00:00 2001 From: frievoe97 Date: Tue, 16 Apr 2024 10:11:14 +0200 Subject: [PATCH 07/31] start working on plotly facet integration --- .../analysis/population/TripAnalysis.java | 2 +- .../simwrapper/dashboard/TripDashboard.java | 10 +- .../org/matsim/simwrapper/viz/Plotly.java | 28 +- .../simwrapper/dashboard/DashboardTests.java | 2 +- .../mode_share_per_group_dist_ref.csv | 271 ++++++++++++++++++ 5 files changed, 302 insertions(+), 11 deletions(-) create mode 100644 contribs/simwrapper/src/test/resources/mode_share_per_group_dist_ref.csv diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java index f91d7598ff4..ee9186e8b3b 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java @@ -39,7 +39,7 @@ @CommandSpec( requires = {"trips.csv", "persons.csv"}, produces = {"mode_share.csv", "mode_share_per_dist.csv", "mode_users.csv", "trip_stats.csv", - "mode_share_per_%s.csv", "population_trip_stats.csv", "trip_purposes_by_hour.csv"} + "mode_share_per_%s.csv", "population_trip_stats.csv", "trip_purposes_by_hour.csv", "mode_share_per_age.csv"} ) public class TripAnalysis implements MATSimAppCommand { diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index 3f15a067e8a..f9380d41646 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -247,7 +247,8 @@ public void configure(Header header, Layout layout) { if (groupedRefData != null) { - layout.row("arrivals").el(Plotly.class, (viz, data) -> { + // age,economic_status,dist_group,main_mode,share + layout.row("facets").el(Plotly.class, (viz, data) -> { viz.title = "FACETS"; viz.description = "by hour and purpose"; @@ -258,10 +259,11 @@ public void configure(Header header, Layout layout) { .build(); viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT).build(), - viz.addDataset(data.compute(TripAnalysis.class, "trip_purposes_by_hour.csv")).mapping() + viz.addDataset(data.compute(TripAnalysis.class, "mode_share_per_age.csv")).mapping() + .facetCol("age") .name("purpose", ColorScheme.Spectral) - .x("h") - .y("arrival") + .x("dist_group") + .y("values") ); }); diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Plotly.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Plotly.java index 6006a297e98..9d6341e0634 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Plotly.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Plotly.java @@ -322,9 +322,9 @@ enum ColumnType { TEXT, SIZE, COLOR, - OPACITY - - // TODO: facet col und row + OPACITY, + FACET_COL, + FACET_ROW, } /** @@ -355,6 +355,8 @@ public static final class DataSet { private Map pivot; private Map constant; private Map aggregate; + private Map normalize; + private Map rename; private DataSet(String file, String name) { this.file = file; @@ -364,7 +366,9 @@ private DataSet(String file, String name) { private Object toJSON() { if (pivot == null && constant == null && - aggregate == null) + aggregate == null && + normalize == null && + rename == null) return file; // TODO: normalize und rename optionen @@ -511,7 +515,21 @@ public DataMapping opacity(String columnName) { return this; } - // TODO: facet col und row im trace + /** + * Mapping for facet_col column. + */ + public DataMapping facetCol(String columnName) { + columns.put(ColumnType.FACET_COL, columnName); + return this; + } + + /** + * Mapping for facet_row column. + */ + public DataMapping facetRow(String columnName) { + columns.put(ColumnType.FACET_ROW, columnName); + return this; + } private void insert(Map obj) { diff --git a/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java b/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java index cd5b0b2a9f5..dcb540e4caa 100644 --- a/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java +++ b/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java @@ -79,7 +79,7 @@ void tripRef() { TripDashboard dashboard = new TripDashboard("mode_share_ref.csv", "mode_share_per_dist_ref.csv", "mode_users_ref.csv"); - dashboard.withGroupedRefData("grouped_ref.csv"); + dashboard.withGroupedRefData("mode_share_per_group_dist_ref.csv"); run(dashboard); Assertions.assertThat(out) diff --git a/contribs/simwrapper/src/test/resources/mode_share_per_group_dist_ref.csv b/contribs/simwrapper/src/test/resources/mode_share_per_group_dist_ref.csv new file mode 100644 index 00000000000..7ab70a67d9d --- /dev/null +++ b/contribs/simwrapper/src/test/resources/mode_share_per_group_dist_ref.csv @@ -0,0 +1,271 @@ +age,economic_status,dist_group,main_mode,share +,,0 - 1000,bike,0.03993953574665 +,,0 - 1000,car,0.010077686113465548 +,,0 - 1000,pt,0.0035575461836548946 +,,0 - 1000,ride,0.0037969819009802397 +,,0 - 1000,walk,0.22881070677001764 +,,1000 - 2000,bike,0.04698342341346555 +,,1000 - 2000,car,0.023147292190076742 +,,1000 - 2000,pt,0.02007970854535886 +,,1000 - 2000,ride,0.010215459406613094 +,,1000 - 2000,walk,0.05220619945665914 +,,2000 - 5000,bike,0.05624516000252578 +,,2000 - 5000,car,0.05164117094781402 +,,2000 - 5000,pt,0.07035847284671681 +,,2000 - 5000,ride,0.019753111426262702 +,,2000 - 5000,walk,0.013501281662145714 +,,5000 - 10000,bike,0.02693069701653698 +,,5000 - 10000,car,0.05215320525856776 +,,5000 - 10000,pt,0.08244750747280563 +,,5000 - 10000,ride,0.01397122077983446 +,,5000 - 10000,walk,0.001517535520605139 +,,10000 - 20000,bike,0.007136782872703979 +,,10000 - 20000,car,0.04134445948649564 +,,10000 - 20000,pt,0.06385899348452925 +,,10000 - 20000,ride,0.007926042173942492 +,,10000 - 20000,walk,0.0005273153061550004 +,,20000+,bike,0.0006419443250597948 +,,20000+,car,0.022309302342807483 +,,20000+,pt,0.024771142628339887 +,,20000+,ride,0.003944497517436229 +,,20000+,walk,0.00020561720177352156 +0 - 18,,0 - 1000,bike,0.07229378725286068 +0 - 18,,0 - 1000,car,2.2787447948172964e-05 +0 - 18,,0 - 1000,pt,0.0032885333542058675 +0 - 18,,0 - 1000,ride,0.014726340770922693 +0 - 18,,0 - 1000,walk,0.27842606902732 +0 - 18,,1000 - 2000,bike,0.08569283527359176 +0 - 18,,1000 - 2000,car,9.9737820478634e-05 +0 - 18,,1000 - 2000,pt,0.02591594763943778 +0 - 18,,1000 - 2000,ride,0.0405848317671109 +0 - 18,,1000 - 2000,walk,0.06385876158993227 +0 - 18,,2000 - 5000,bike,0.06412457627408044 +0 - 18,,2000 - 5000,car,0.00013846416722995588 +0 - 18,,2000 - 5000,pt,0.10245573841407069 +0 - 18,,2000 - 5000,ride,0.0696453433344005 +0 - 18,,2000 - 5000,walk,0.008117395318933516 +0 - 18,,5000 - 10000,bike,0.007277279625603125 +0 - 18,,5000 - 10000,car,0.00013224825537415898 +0 - 18,,5000 - 10000,pt,0.07106402349407442 +0 - 18,,5000 - 10000,ride,0.03997163402508548 +0 - 18,,5000 - 10000,walk,0.0007362455953801282 +0 - 18,,10000 - 20000,bike,0.0005175567280619023 +0 - 18,,10000 - 20000,car,5.133475935144192e-05 +0 - 18,,10000 - 20000,pt,0.023911770521047097 +0 - 18,,10000 - 20000,ride,0.01566996165519817 +0 - 18,,10000 - 20000,walk,0.00027820875574264116 +0 - 18,,20000+,bike,0.00014888881816754997 +0 - 18,,20000+,car,0.0 +0 - 18,,20000+,pt,0.00541683946477697 +0 - 18,,20000+,ride,0.005078946237578967 +0 - 18,,20000+,walk,0.00035391261203410667 +18 - 66,,0 - 1000,bike,0.035843127186867024 +18 - 66,,0 - 1000,car,0.010335065645936097 +18 - 66,,0 - 1000,pt,0.0024306518695817693 +18 - 66,,0 - 1000,ride,0.0012660481672359296 +18 - 66,,0 - 1000,walk,0.2041146382136089 +18 - 66,,1000 - 2000,bike,0.043352649338563545 +18 - 66,,1000 - 2000,car,0.024096827714692407 +18 - 66,,1000 - 2000,pt,0.015542882507096583 +18 - 66,,1000 - 2000,ride,0.0035363974027805417 +18 - 66,,1000 - 2000,walk,0.045659194729557075 +18 - 66,,2000 - 5000,bike,0.0631398064777341 +18 - 66,,2000 - 5000,car,0.054818071432069675 +18 - 66,,2000 - 5000,pt,0.06482579238881256 +18 - 66,,2000 - 5000,ride,0.00832075054319505 +18 - 66,,2000 - 5000,walk,0.01333842505167683 +18 - 66,,5000 - 10000,bike,0.03637397303511378 +18 - 66,,5000 - 10000,car,0.060555551635949174 +18 - 66,,5000 - 10000,pt,0.09229605293671034 +18 - 66,,5000 - 10000,ride,0.00759953611299685 +18 - 66,,5000 - 10000,walk,0.001544428102195918 +18 - 66,,10000 - 20000,bike,0.009833559150348366 +18 - 66,,10000 - 20000,car,0.05152644339404211 +18 - 66,,10000 - 20000,pt,0.07923721033910733 +18 - 66,,10000 - 20000,ride,0.005266544462625669 +18 - 66,,10000 - 20000,walk,0.00047023586422664473 +18 - 66,,20000+,bike,0.0008439707548347554 +18 - 66,,20000+,car,0.028513445657508366 +18 - 66,,20000+,pt,0.032056642580330474 +18 - 66,,20000+,ride,0.0030548198681034284 +18 - 66,,20000+,walk,0.00020725743649875283 +66+,,0 - 1000,bike,0.028917781379748875 +66+,,0 - 1000,car,0.01756905891685044 +66+,,0 - 1000,pt,0.008273204993617672 +66+,,0 - 1000,ride,0.004628191849485549 +66+,,0 - 1000,walk,0.2851630822571291 +66+,,1000 - 2000,bike,0.02854964969272559 +66+,,1000 - 2000,car,0.03882086040651397 +66+,,1000 - 2000,pt,0.0331697357735842 +66+,,1000 - 2000,ride,0.011117551434265606 +66+,,1000 - 2000,walk,0.06845936405951983 +66+,,2000 - 5000,bike,0.022205536519422444 +66+,,2000 - 5000,car,0.08261732385978052 +66+,,2000 - 5000,pt,0.0650551591678631 +66+,,2000 - 5000,ride,0.023067556374726784 +66+,,2000 - 5000,walk,0.01871777257889785 +66+,,5000 - 10000,bike,0.0060053800450549635 +66+,,5000 - 10000,car,0.0627074379519613 +66+,,5000 - 10000,pt,0.05292597833638581 +66+,,5000 - 10000,ride,0.0172732713428096 +66+,,5000 - 10000,walk,0.0020729972598010397 +66+,,10000 - 20000,bike,0.0020118745335472974 +66+,,10000 - 20000,car,0.03570337079193992 +66+,,10000 - 20000,pt,0.03652161682334497 +66+,,10000 - 20000,ride,0.011929475838790412 +66+,,10000 - 20000,walk,0.0009657233478543096 +66+,,20000+,bike,0.00025574980638623377 +66+,,20000+,car,0.01651491485906698 +66+,,20000+,pt,0.012176960792435333 +66+,,20000+,ride,0.006529537524723665 +66+,,20000+,walk,7.388148176658781e-05 +,very_low,0 - 1000,bike,0.03470681238249625 +,very_low,0 - 1000,car,0.00664566014131732 +,very_low,0 - 1000,pt,0.00760869670923257 +,very_low,0 - 1000,ride,0.003124728592942877 +,very_low,0 - 1000,walk,0.24469895082287613 +,very_low,1000 - 2000,bike,0.04568124665405379 +,very_low,1000 - 2000,car,0.01762264405555989 +,very_low,1000 - 2000,pt,0.03176643067456601 +,very_low,1000 - 2000,ride,0.009316330422067991 +,very_low,1000 - 2000,walk,0.06872298007466462 +,very_low,2000 - 5000,bike,0.050899816256695075 +,very_low,2000 - 5000,car,0.033749602344892396 +,very_low,2000 - 5000,pt,0.09486902097108738 +,very_low,2000 - 5000,ride,0.015788943796908508 +,very_low,2000 - 5000,walk,0.017570734627488975 +,very_low,5000 - 10000,bike,0.02275352649147605 +,very_low,5000 - 10000,car,0.027723850662754297 +,very_low,5000 - 10000,pt,0.10839849171594336 +,very_low,5000 - 10000,ride,0.012009036032519245 +,very_low,5000 - 10000,walk,0.0015394033588941112 +,very_low,10000 - 20000,bike,0.004407524031423659 +,very_low,10000 - 20000,car,0.019387817923563564 +,very_low,10000 - 20000,pt,0.0734242401915033 +,very_low,10000 - 20000,ride,0.004587556923703756 +,very_low,10000 - 20000,walk,0.001209125736696242 +,very_low,20000+,bike,0.00041095332498231995 +,very_low,20000+,car,0.009550733296281531 +,very_low,20000+,pt,0.027400397002285904 +,very_low,20000+,ride,0.004042413867625509 +,very_low,20000+,walk,0.00038233091349725186 +,low,0 - 1000,bike,0.041246470043080165 +,low,0 - 1000,car,0.008910466432937744 +,low,0 - 1000,pt,0.004293903536684401 +,low,0 - 1000,ride,0.0033071684223682056 +,low,0 - 1000,walk,0.24947973724879838 +,low,1000 - 2000,bike,0.04961773460207695 +,low,1000 - 2000,car,0.019329953900366764 +,low,1000 - 2000,pt,0.025483245683043533 +,low,1000 - 2000,ride,0.009185948988387805 +,low,1000 - 2000,walk,0.05237722044190244 +,low,2000 - 5000,bike,0.052466234584333846 +,low,2000 - 5000,car,0.04278374195939727 +,low,2000 - 5000,pt,0.08432283560598662 +,low,2000 - 5000,ride,0.01702898827541261 +,low,2000 - 5000,walk,0.015885053481139822 +,low,5000 - 10000,bike,0.022672784003795167 +,low,5000 - 10000,car,0.04475208451328922 +,low,5000 - 10000,pt,0.09238030305339276 +,low,5000 - 10000,ride,0.012354387654785704 +,low,5000 - 10000,walk,0.0013482692004642123 +,low,10000 - 20000,bike,0.00606581254281994 +,low,10000 - 20000,car,0.031209843665855812 +,low,10000 - 20000,pt,0.0653262065900355 +,low,10000 - 20000,ride,0.006909609553827318 +,low,10000 - 20000,walk,0.0005263054577439737 +,low,20000+,bike,0.00047003197176413144 +,low,20000+,car,0.013483593316552238 +,low,20000+,pt,0.023496583359092254 +,low,20000+,ride,0.0032589486332435533 +,low,20000+,walk,2.6533277421522046e-05 +,medium,0 - 1000,bike,0.03911700063927027 +,medium,0 - 1000,car,0.011217731375356293 +,medium,0 - 1000,pt,0.0036674145395910263 +,medium,0 - 1000,ride,0.0040009596846765074 +,medium,0 - 1000,walk,0.2331501383860879 +,medium,1000 - 2000,bike,0.04558521294999408 +,medium,1000 - 2000,car,0.02544280536333108 +,medium,1000 - 2000,pt,0.019434896147692903 +,medium,1000 - 2000,ride,0.009855997679676024 +,medium,1000 - 2000,walk,0.0537914970446092 +,medium,2000 - 5000,bike,0.05473092890667934 +,medium,2000 - 5000,car,0.05384396289524313 +,medium,2000 - 5000,pt,0.06617655580215061 +,medium,2000 - 5000,ride,0.01916203987731777 +,medium,2000 - 5000,walk,0.013040227239873697 +,medium,5000 - 10000,bike,0.02574636249329226 +,medium,5000 - 10000,car,0.05641048735516864 +,medium,5000 - 10000,pt,0.08034283586815048 +,medium,5000 - 10000,ride,0.01428950786852113 +,medium,5000 - 10000,walk,0.0018418487953256962 +,medium,10000 - 20000,bike,0.006346523947575605 +,medium,10000 - 20000,car,0.04150377161860774 +,medium,10000 - 20000,pt,0.0629525618466731 +,medium,10000 - 20000,ride,0.0074802925048469545 +,medium,10000 - 20000,walk,0.0003904121475787536 +,medium,20000+,bike,0.000680433218446468 +,medium,20000+,car,0.021857757194240046 +,medium,20000+,pt,0.023834126105462355 +,medium,20000+,ride,0.003906827817956772 +,medium,20000+,walk,0.00019888268660406687 +,high,0 - 1000,bike,0.041939214472666166 +,high,0 - 1000,car,0.009296976205872368 +,high,0 - 1000,pt,0.0019245320576424141 +,high,0 - 1000,ride,0.0029544349795922243 +,high,0 - 1000,walk,0.21013293332150743 +,high,1000 - 2000,bike,0.046923469208975645 +,high,1000 - 2000,car,0.022359163419126016 +,high,1000 - 2000,pt,0.01598105459224138 +,high,1000 - 2000,ride,0.00969781710878335 +,high,1000 - 2000,walk,0.04629924755619724 +,high,2000 - 5000,bike,0.06385560203538197 +,high,2000 - 5000,car,0.05664195116716653 +,high,2000 - 5000,pt,0.06341010532730178 +,high,2000 - 5000,ride,0.02062977816711411 +,high,2000 - 5000,walk,0.013022335460471058 +,high,5000 - 10000,bike,0.03340249625957814 +,high,5000 - 10000,car,0.05522082435189495 +,high,5000 - 10000,pt,0.0768163134346059 +,high,5000 - 10000,ride,0.014151567281204704 +,high,5000 - 10000,walk,0.0010048399305621642 +,high,10000 - 20000,bike,0.009283542374603758 +,high,10000 - 20000,car,0.05082160352876232 +,high,10000 - 20000,pt,0.06361373744580245 +,high,10000 - 20000,ride,0.009672528719534406 +,high,10000 - 20000,walk,0.000206579718736878 +,high,20000+,bike,0.00043427613503130015 +,high,20000+,car,0.0302730459828126 +,high,20000+,pt,0.025319864147407976 +,high,20000+,ride,0.004383208486815366 +,high,20000+,walk,0.00032695712260737283 +,very_high,0 - 1000,bike,0.04115639089698632 +,very_high,0 - 1000,car,0.012722254325776056 +,very_high,0 - 1000,pt,0.0021241404664278096 +,very_high,0 - 1000,ride,0.006479698270689627 +,very_high,0 - 1000,walk,0.21073962672422653 +,very_high,1000 - 2000,bike,0.04962428972352181 +,very_high,1000 - 2000,car,0.02750613621612044 +,very_high,1000 - 2000,pt,0.013034837734497939 +,very_high,1000 - 2000,ride,0.015305658534843499 +,very_high,1000 - 2000,walk,0.044574423999390184 +,very_high,2000 - 5000,bike,0.054368817956349784 +,very_high,2000 - 5000,car,0.06181110351680053 +,very_high,2000 - 5000,pt,0.05826964813749158 +,very_high,2000 - 5000,ride,0.02786841949326947 +,very_high,2000 - 5000,walk,0.008853536562096663 +,very_high,5000 - 10000,bike,0.02620750926316137 +,very_high,5000 - 10000,car,0.06319670192405069 +,very_high,5000 - 10000,pt,0.06408662985856173 +,very_high,5000 - 10000,ride,0.016694136150129876 +,very_high,5000 - 10000,walk,0.0017755406053744846 +,very_high,10000 - 20000,bike,0.00916609078166196 +,very_high,10000 - 20000,car,0.05422199392062498 +,very_high,10000 - 20000,pt,0.05651365669095907 +,very_high,10000 - 20000,ride,0.010116960000757621 +,very_high,10000 - 20000,walk,0.0011846243905422437 +,very_high,20000+,bike,0.001489759414370527 +,very_high,20000+,car,0.03041067782980122 +,very_high,20000+,pt,0.026469066188086976 +,very_high,20000+,ride,0.003984235726180184 +,very_high,20000+,walk,4.343469724887364e-05 From 8a2b3b4318103d4d4c5bbb66a7e8ba4da4763d6a Mon Sep 17 00:00:00 2001 From: frievoe97 Date: Tue, 16 Apr 2024 15:23:03 +0200 Subject: [PATCH 08/31] Added Test Trip Dashboard for Facets --- .../org/matsim/simwrapper/dashboard/TripDashboard.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index f9380d41646..cbb1bfe914b 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -253,17 +253,18 @@ public void configure(Header header, Layout layout) { viz.title = "FACETS"; viz.description = "by hour and purpose"; viz.layout = tech.tablesaw.plotly.components.Layout.builder() - .xAxis(Axis.builder().title("Hour").build()) - .yAxis(Axis.builder().title("Share").build()) + .xAxis(Axis.builder().title("dist_group").build()) + .yAxis(Axis.builder().title("sim_share").build()) .barMode(tech.tablesaw.plotly.components.Layout.BarMode.STACK) .build(); + // TODO: Still in testing viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT).build(), viz.addDataset(data.compute(TripAnalysis.class, "mode_share_per_age.csv")).mapping() .facetCol("age") - .name("purpose", ColorScheme.Spectral) + .name("main_mode", ColorScheme.Spectral) .x("dist_group") - .y("values") + .y("sim_share") ); }); From daeab37b57e108b47bb887c5a4bfefcd74e4d44c Mon Sep 17 00:00:00 2001 From: rakow Date: Tue, 16 Apr 2024 18:04:20 +0200 Subject: [PATCH 09/31] update API --- .../analysis/population/Category.java | 72 ++++++++++++++++++- .../simwrapper/dashboard/TripDashboard.java | 28 ++++++-- .../org/matsim/simwrapper/viz/Plotly.java | 38 +++++++--- 3 files changed, 120 insertions(+), 18 deletions(-) diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/Category.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/Category.java index bddf50a0cb4..3165b46d9ee 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/Category.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/Category.java @@ -1,6 +1,10 @@ package org.matsim.application.analysis.population; +import org.matsim.core.config.ReflectiveConfigGroup; +import org.matsim.utils.objectattributes.attributable.Attributes; + import java.util.*; +import java.util.regex.Pattern; /** * Categorize values into groups. @@ -21,6 +25,12 @@ public final class Category { */ private final Map grouped; + + /** + * Regular expressions for each category. + */ + private final Map regex; + /** * Range categories. */ @@ -29,6 +39,7 @@ public final class Category { public Category(Set values) { this.values = values; this.grouped = new HashMap<>(); + this.regex = new HashMap<>(); for (String v : values) { if (v.contains(",")) { String[] grouped = v.split(","); @@ -36,6 +47,10 @@ public Category(Set values) { this.grouped.put(g, v); } } + + if (v.startsWith("/") && v.endsWith("/")) { + this.regex.put(v, Pattern.compile(v.substring(1, v.length() - 1), Pattern.CASE_INSENSITIVE)); + } } boolean range = this.values.stream().allMatch(v -> v.contains("-") || v.contains("+")); @@ -66,6 +81,42 @@ public Category(Set values) { } } + /** + * Create categories from config parameters. + */ + public static Map fromConfigParams(Collection params) { + + Map> categories = new HashMap<>(); + + // Collect all values + for (ReflectiveConfigGroup parameter : params) { + for (Map.Entry kv : parameter.getParams().entrySet()) { + categories.computeIfAbsent(kv.getKey(), k -> new HashSet<>()).add(kv.getValue()); + } + } + + return categories.entrySet().stream() + .collect(HashMap::new, (m, e) -> m.put(e.getKey(), new Category(e.getValue())), HashMap::putAll); + } + + /** + * Match attributes from an object with parameters defined in config. + */ + public static boolean matchAttributesWithConfig(Attributes attr, ReflectiveConfigGroup config, Map categories) { + + for (Map.Entry e : config.getParams().entrySet()) { + // might be null if not defined + Object objValue = attr.getAttribute(e.getKey()); + String category = categories.get(e.getKey()).categorize(objValue); + + // compare as string + if (!Objects.toString(category).equals(e.getValue())) + return false; + } + + return true; + } + /** * Categorize a single value. */ @@ -85,6 +136,12 @@ public String categorize(Object value) { return v; else if (grouped.containsKey(v)) return grouped.get(v); + else { + for (Map.Entry kv : regex.entrySet()) { + if (kv.getValue().matcher(v).matches()) + return kv.getKey(); + } + } try { double d = Double.parseDouble(v); @@ -120,14 +177,23 @@ else if (grouped.containsKey(v)) return null; } + @Override + public String toString() { + return "Category{" + + "values=" + values + + (grouped != null && !grouped.isEmpty() ? ", grouped=" + grouped : "") + + (regex != null && !regex.isEmpty() ? ", regex=" + regex : "") + + (ranges != null && !ranges.isEmpty() ? ", ranges=" + ranges : "") + + '}'; + } + /** + * Number range. + * * @param left Left bound of the range. * @param right Right bound of the range. (exclusive) * @param label Label of this group. */ private record Range(double left, double right, String label) { - - } - } diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index cbb1bfe914b..9c34d85f4ff 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -26,7 +26,10 @@ public class TripDashboard implements Dashboard { @Nullable private final String modeUsersRefCsv; - private String groupedRefData; + @Nullable + private String groupedRefCsv; + @Nullable + private String[] categories; private String[] args; @@ -55,9 +58,15 @@ public TripDashboard(@Nullable String modeShareRefCsv, @Nullable String modeShar /** * Set grouped reference data. Will enable additional tab with analysis for subgroups of the population. + * @param groupedRefCsv resource containing the grouped reference data + * @param categories categories to show on dashboard, if empty all categories will be used */ - public TripDashboard withGroupedRefData(String groupedRefData) { - this.groupedRefData = groupedRefData; + public TripDashboard withGroupedRefData(String groupedRefCsv, String... categories) { + this.groupedRefCsv = groupedRefCsv; + if (categories.length == 0) { + categories = detectCategories(groupedRefCsv); + } + this.categories = categories; return this; } @@ -69,19 +78,24 @@ public TripDashboard setAnalysisArgs(String... args) { return this; } + private static String[] detectCategories(String groupedRefCsv) { + // TODO: Implement + return new String[0]; + } + @Override public void configure(Header header, Layout layout) { header.title = "Trips"; header.description = "General information about modal share and trip distributions."; - String[] args = new String[this.groupedRefData == null ? this.args.length : this.args.length + 2]; + String[] args = new String[this.groupedRefCsv == null ? this.args.length : this.args.length + 2]; System.arraycopy(this.args, 0, args, 0, this.args.length); // Add ref data to the argument if set - if (groupedRefData != null) { + if (groupedRefCsv != null) { args[this.args.length] = "--input-ref-data"; - args[this.args.length + 1] = groupedRefData; + args[this.args.length + 1] = groupedRefCsv; } Layout.Row first = layout.row("first"); @@ -245,7 +259,7 @@ public void configure(Header header, Layout layout) { }); - if (groupedRefData != null) { + if (groupedRefCsv != null) { // age,economic_status,dist_group,main_mode,share layout.row("facets").el(Plotly.class, (viz, data) -> { diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Plotly.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Plotly.java index 9d6341e0634..36d01346f58 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Plotly.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Plotly.java @@ -73,11 +73,11 @@ public final class Plotly extends Viz { public Map multiIndex; @JsonIgnore - private List traces = new ArrayList<>(); + private final List traces = new ArrayList<>(); @JsonIgnore - private List data = new ArrayList<>(); + private final List data = new ArrayList<>(); @JsonIgnore - private List mappings = new ArrayList<>(); + private final List mappings = new ArrayList<>(); public Plotly() { super("plotly"); @@ -138,7 +138,7 @@ public DataSet addDataset(String path) { Objects.requireNonNull(path, "Path argument can not be null"); - String name = data.size() == 0 ? "dataset" : FilenameUtils.removeExtension(FilenameUtils.getName(path)).replace("_", ""); + String name = data.isEmpty() ? "dataset" : FilenameUtils.removeExtension(FilenameUtils.getName(path)).replace("_", ""); DataSet ds = new DataSet(path, name); data.add(ds); return ds; @@ -167,7 +167,7 @@ public Plotly fromFigure(Figure figure) { @JsonProperty(value = "datasets", index = 20) Map getDataSets() { - if (data.size() == 0) + if (data.isEmpty()) return null; Map result = new LinkedHashMap<>(); @@ -371,8 +371,6 @@ private Object toJSON() { rename == null) return file; - // TODO: normalize und rename optionen - return this; } @@ -424,6 +422,30 @@ public DataSet aggregate(List groupBy, String target, AggrFunc func) { ); return this; } + + /** + * Normalize data to 100% for each group. + * @param groupBy group columns to group by. + * @param target target column. + */ + public DataSet normalize(List groupBy, String target) { + normalize = Map.of( + "groupBy", groupBy, + "target", target + ); + return this; + } + + /** + * Rename values in the dataset. Useful after pivot or to rename certain entries. + */ + public DataSet rename(String oldName, String newName) { + if (rename == null) + rename = new LinkedHashMap<>(); + + rename.put(oldName, newName); + return this; + } } /** @@ -432,7 +454,7 @@ public DataSet aggregate(List groupBy, String target, AggrFunc func) { public static final class DataMapping { private final String ref; - private Map columns = new EnumMap<>(ColumnType.class); + private final Map columns = new EnumMap<>(ColumnType.class); private String colorRamp; From 9b5955c7697c1360bd6be0e5fd3f825990842900 Mon Sep 17 00:00:00 2001 From: rakow Date: Sat, 8 Jun 2024 15:35:21 +0200 Subject: [PATCH 10/31] add trip choice analysis --- .../analysis/population/TripAnalysis.java | 29 ++++- .../population/TripByGroupAnalysis.java | 2 +- .../population/TripChoiceAnalysis.java | 100 ++++++++++++++++++ 3 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java index ee9186e8b3b..02c3fd676d8 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java @@ -38,13 +38,23 @@ @CommandLine.Command(name = "trips", description = "Calculates various trip related metrics.") @CommandSpec( requires = {"trips.csv", "persons.csv"}, - produces = {"mode_share.csv", "mode_share_per_dist.csv", "mode_users.csv", "trip_stats.csv", - "mode_share_per_%s.csv", "population_trip_stats.csv", "trip_purposes_by_hour.csv", "mode_share_per_age.csv"} + produces = { + "mode_share.csv", "mode_share_per_dist.csv", "mode_users.csv", "trip_stats.csv", + "mode_share_per_%s.csv", "population_trip_stats.csv", "trip_purposes_by_hour.csv", + "mode_choices.csv", "mode_choice_evaluation.csv", "mode_choice_evaluation_per_mode.csv" + } ) public class TripAnalysis implements MATSimAppCommand { private static final Logger log = LogManager.getLogger(TripAnalysis.class); - + /** + * Person attribute that contains the reference modes of a person. + */ + public static String ATTR_REF_MODES = "ref_modes"; + /** + * Person attribute containing its weight for analysis purposes. + */ + public static String ATTR_REF_WEIGHT = "ref_weight"; @CommandLine.Mixin private InputOptions input = InputOptions.ofCommand(TripAnalysis.class); @CommandLine.Mixin @@ -209,6 +219,18 @@ public Integer call() throws Exception { groups.analyzeModeShare(joined, labels, (g) -> output.getPath("mode_share_per_%s.csv", g)); } + if (persons.containsColumn(ATTR_REF_MODES)) { + try { + TripChoiceAnalysis choices = new TripChoiceAnalysis(persons, trips, modeOrder); + + choices.writeChoices(output.getPath("mode_choices.csv")); + choices.writeChoiceEvaluation(output.getPath("mode_choice_evaluation.csv")); + choices.writeChoiceEvaluationPerMode(output.getPath("mode_choice_evaluation_per_mode.csv")); + } catch (RuntimeException e) { + log.error("Error while analyzing mode choices", e); + } + } + writePopulationStats(persons, joined); writeTripStats(joined); @@ -358,7 +380,6 @@ private void writePopulationStats(Table persons, Table trips) throws IOException table.write().csv(output.getPath("mode_users.csv").toFile()); try (CSVPrinter printer = new CSVPrinter(Files.newBufferedWriter(output.getPath("population_trip_stats.csv")), CSVFormat.DEFAULT)) { - printer.printRecord("Info", "Value"); printer.printRecord("Persons", tripsPerPerson.size()); printer.printRecord("Mobile persons [%]", new BigDecimal(100 * totalMobile / tripsPerPerson.size()).setScale(2, RoundingMode.HALF_UP)); diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java index 62d95d0a152..59f669ad159 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java @@ -57,7 +57,7 @@ final class TripByGroupAnalysis { groups.add(g); } - log.info("Detect groups: {}", groups); + log.info("Detected groups: {}", groups); this.groups = new ArrayList<>(); diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java new file mode 100644 index 00000000000..ce4fa6727ec --- /dev/null +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java @@ -0,0 +1,100 @@ +package org.matsim.application.analysis.population; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import tech.tablesaw.api.Row; +import tech.tablesaw.api.Table; +import tech.tablesaw.joining.DataFrameJoiner; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +/** + * Helper class to analyze trip choices from persons against reference data. + * Metrics for binary classification + * Evaluation multi-class classifiers + * more + * more + * ... + */ +final class TripChoiceAnalysis { + + private static final Logger log = LogManager.getLogger(TripChoiceAnalysis.class); + + private final List modeOrder; + + /** + * Contains trip data with true and predicated (simulated) modes. + */ + private final List data = new ArrayList<>(); + + public TripChoiceAnalysis(Table persons, Table trips, List modeOrder) { + persons = persons.where(persons.stringColumn("ref_modes").isNotEqualTo("")); + trips = new DataFrameJoiner(trips, "person").inner(persons);; + this.modeOrder = modeOrder; + + log.info("Analyzing mode choices for {} persons", persons.rowCount()); + + for (Row trip : trips) { + + String person = trip.getText("person"); + int n = trip.getInt("trip_number") - 1; + double weight = trip.getDouble(TripAnalysis.ATTR_REF_WEIGHT); + + String predMode = trip.getString("main_mode"); + String[] split = trip.getString(TripAnalysis.ATTR_REF_MODES).split("-"); + + if (n < split.length) { + String trueMode = split[n]; + data.add(new Entry(person, weight, n, trueMode, predMode)); + } else + log.warn("Person {} trip {} does not match ref data ({})", person, n, split.length); + } + } + + /** + * Writes all choices to csv. + */ + public void writeChoices(Path path) throws IOException { + try (CSVPrinter csv = new CSVPrinter(Files.newBufferedWriter(path), CSVFormat.DEFAULT)) { + csv.printRecord("person", "weight", "true_mode", "pred_mode"); + for (Entry e : data) { + csv.printRecord(e.person, e.weight, e.trueMode, e.predMode); + } + } + } + + /** + * Writes aggregated choices metrics. + */ + public void writeChoiceEvaluation(Path path) throws IOException { + + try (CSVPrinter csv = new CSVPrinter(Files.newBufferedWriter(path), CSVFormat.DEFAULT)) { + + csv.printRecord("Info", "Value"); + + csv.printRecord("Accuracy", "TODO"); + + + } + + + // TODO: accuracy + // macro and micro averaged precision, recall, f1 + } + + /** + * Writes metrics per mode. + */ + public void writeChoiceEvaluationPerMode(Path path) { + + } + + private record Entry(String person, double weight, int n, String trueMode, String predMode) { + } +} From 67dd1ca6cf3e467ea1af64f151145ce3821875c1 Mon Sep 17 00:00:00 2001 From: rakow Date: Sat, 8 Jun 2024 16:40:36 +0200 Subject: [PATCH 11/31] adding test case and some todos --- .../population/TripChoiceAnalysis.java | 34 +++++++++++++----- .../simwrapper/dashboard/TripDashboard.java | 2 +- .../org/matsim/simwrapper/TestScenario.java | 36 +++++++++++++++++++ .../simwrapper/dashboard/DashboardTests.java | 5 ++- 4 files changed, 65 insertions(+), 12 deletions(-) diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java index ce4fa6727ec..2342c4a49ff 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java @@ -35,16 +35,18 @@ final class TripChoiceAnalysis { public TripChoiceAnalysis(Table persons, Table trips, List modeOrder) { persons = persons.where(persons.stringColumn("ref_modes").isNotEqualTo("")); - trips = new DataFrameJoiner(trips, "person").inner(persons);; + trips = new DataFrameJoiner(trips, "person").inner(persons); this.modeOrder = modeOrder; log.info("Analyzing mode choices for {} persons", persons.rowCount()); + boolean hasWeight = persons.containsColumn(TripAnalysis.ATTR_REF_WEIGHT); + for (Row trip : trips) { String person = trip.getText("person"); int n = trip.getInt("trip_number") - 1; - double weight = trip.getDouble(TripAnalysis.ATTR_REF_WEIGHT); + double weight = hasWeight ? trip.getDouble(TripAnalysis.ATTR_REF_WEIGHT) : 1; String predMode = trip.getString("main_mode"); String[] split = trip.getString(TripAnalysis.ATTR_REF_MODES).split("-"); @@ -62,9 +64,9 @@ public TripChoiceAnalysis(Table persons, Table trips, List modeOrder) { */ public void writeChoices(Path path) throws IOException { try (CSVPrinter csv = new CSVPrinter(Files.newBufferedWriter(path), CSVFormat.DEFAULT)) { - csv.printRecord("person", "weight", "true_mode", "pred_mode"); + csv.printRecord("person", "weight", "n", "true_mode", "pred_mode"); for (Entry e : data) { - csv.printRecord(e.person, e.weight, e.trueMode, e.predMode); + csv.printRecord(e.person, e.weight, e.n, e.trueMode, e.predMode); } } } @@ -79,22 +81,38 @@ public void writeChoiceEvaluation(Path path) throws IOException { csv.printRecord("Info", "Value"); csv.printRecord("Accuracy", "TODO"); + csv.printRecord("F1 Score", "TODO"); +// csv.printRecord("AUC-ROC", ""); + csv.printRecord("Precision", "TODO"); + csv.printRecord("Recall", "TODO"); + // These can be micro and macro averaged } - - // TODO: accuracy - // macro and micro averaged precision, recall, f1 + // TODO Cohen’s Kappa, Cross-Entropy, Mathews Correlation Coefficient (MCC) } /** * Writes metrics per mode. */ - public void writeChoiceEvaluationPerMode(Path path) { + public void writeChoiceEvaluationPerMode(Path path) throws IOException { + + try (CSVPrinter csv = new CSVPrinter(Files.newBufferedWriter(path), CSVFormat.DEFAULT)) { + + csv.printRecord("Accuracy", "TODO"); + csv.printRecord("Precision", "TODO"); + csv.printRecord("Recall", "TODO"); + csv.printRecord("F1 Score", "TODO"); + + } } + // TODO: write confusion matrix + + // TODO Class Prediction Error + private record Entry(String person, double weight, int n, String trueMode, String predMode) { } } diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index 9c34d85f4ff..315943d8333 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -274,7 +274,7 @@ public void configure(Header header, Layout layout) { // TODO: Still in testing viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT).build(), - viz.addDataset(data.compute(TripAnalysis.class, "mode_share_per_age.csv")).mapping() + viz.addDataset(data.computeWithPlaceholder(TripAnalysis.class, "mode_share_per_%s.csv", "age")).mapping() .facetCol("age") .name("main_mode", ColorScheme.Spectral) .x("dist_group") diff --git a/contribs/simwrapper/src/test/java/org/matsim/simwrapper/TestScenario.java b/contribs/simwrapper/src/test/java/org/matsim/simwrapper/TestScenario.java index a58095cc3e1..55aa8d54418 100644 --- a/contribs/simwrapper/src/test/java/org/matsim/simwrapper/TestScenario.java +++ b/contribs/simwrapper/src/test/java/org/matsim/simwrapper/TestScenario.java @@ -3,19 +3,27 @@ import com.google.common.collect.Sets; import org.matsim.api.core.v01.Scenario; import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.population.Person; import org.matsim.application.MATSimApplication; +import org.matsim.application.analysis.population.TripAnalysis; import org.matsim.contrib.vsp.scenario.SnzActivities; import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; import org.matsim.core.controler.Controler; import org.matsim.core.controler.OutputDirectoryHierarchy; +import org.matsim.core.router.DefaultAnalysisMainModeIdentifier; +import org.matsim.core.router.TripStructureUtils; import org.matsim.core.utils.io.IOUtils; import org.matsim.examples.ExamplesUtils; import org.matsim.testcases.MatsimTestUtils; import java.net.URL; import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.SplittableRandom; +import java.util.function.Function; +import java.util.stream.Collectors; /** * A test scenario based on kelheim example. @@ -63,6 +71,34 @@ protected void prepareScenario(Scenario scenario) { link.setAllowedModes(newModes); } } + + SplittableRandom rnd = new SplittableRandom(0); + DefaultAnalysisMainModeIdentifier mmi = new DefaultAnalysisMainModeIdentifier(); + + // Generate reference modes randomly + Function genMode = t -> { + double r = rnd.nextDouble(); + if (r < 0.1) + return "car"; + else if (r < 0.2) + return "pt"; + else if (r < 0.3) + return "bike"; + + return mmi.identifyMainMode(t.getLegsOnly()); + }; + + // Assign reference modes to persons + for (Person person : scenario.getPopulation().getPersons().values()) { + + if (rnd.nextDouble() < 0.5) + continue; + + List trips = TripStructureUtils.getTrips(person.getSelectedPlan()); + String ref = trips.stream().map(genMode).collect(Collectors.joining("-")); + + person.getAttributes().putAttribute(TripAnalysis.ATTR_REF_MODES, ref); + } } @Override diff --git a/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java b/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java index dcb540e4caa..34a3a3a3a8c 100644 --- a/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java +++ b/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java @@ -77,9 +77,8 @@ void tripRef() { Path out = Path.of(utils.getOutputDirectory(), "analysis", "population"); - TripDashboard dashboard = new TripDashboard("mode_share_ref.csv", "mode_share_per_dist_ref.csv", "mode_users_ref.csv"); - - dashboard.withGroupedRefData("mode_share_per_group_dist_ref.csv"); + TripDashboard dashboard = new TripDashboard("mode_share_ref.csv", "mode_share_per_dist_ref.csv", "mode_users_ref.csv") + .withGroupedRefData("mode_share_per_group_dist_ref.csv"); run(dashboard); Assertions.assertThat(out) From e1874d96ad59f8c9a430614fa77603ee6ec6ba54 Mon Sep 17 00:00:00 2001 From: rakow Date: Sun, 9 Jun 2024 16:01:10 +0200 Subject: [PATCH 12/31] calculating some trip choice metrics --- .../population/TripChoiceAnalysis.java | 105 +++++++++++++++--- 1 file changed, 91 insertions(+), 14 deletions(-) diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java index 2342c4a49ff..11bf934b524 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java @@ -11,8 +11,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; +import java.util.*; /** * Helper class to analyze trip choices from persons against reference data. @@ -33,6 +32,11 @@ final class TripChoiceAnalysis { */ private final List data = new ArrayList<>(); + /** + * Contains predication result for each mode. + */ + private final Map counts = new HashMap<>(); + public TripChoiceAnalysis(Table persons, Table trips, List modeOrder) { persons = persons.where(persons.stringColumn("ref_modes").isNotEqualTo("")); trips = new DataFrameJoiner(trips, "person").inner(persons); @@ -57,6 +61,43 @@ public TripChoiceAnalysis(Table persons, Table trips, List modeOrder) { } else log.warn("Person {} trip {} does not match ref data ({})", person, n, split.length); } + + for (String mode : modeOrder) { + counts.put(mode, countPredictions(mode, data)); + } + } + + private static double precision(Counts c) { + return c.tp / (c.tp + c.fp); + } + + private static double recall(Counts c) { + return c.tp / (c.tp + c.fn); + } + + private static double f1(Counts c) { + return 2 * c.tp / (2 * c.tp + c.fp + c.fn); + } + + private Counts countPredictions(String mode, List data) { + double tp = 0, fp = 0, fn = 0, tn = 0; + double total = 0; + for (Entry e : data) { + if (e.trueMode.equals(mode)) { + if (e.predMode.equals(mode)) + tp += e.weight; + else + fn += e.weight; + } else { + if (e.predMode.equals(mode)) + fp += e.weight; + else + tn += e.weight; + } + total += e.weight; + } + + return new Counts(tp, fp, fn, tn, total); } /** @@ -76,18 +117,32 @@ public void writeChoices(Path path) throws IOException { */ public void writeChoiceEvaluation(Path path) throws IOException { - try (CSVPrinter csv = new CSVPrinter(Files.newBufferedWriter(path), CSVFormat.DEFAULT)) { + double tp = 0; + double total = 0; + double tpfp = 0; + double tpfn = 0; + for (Counts c : counts.values()) { + tp += c.tp; + tpfp += c.tp + c.fp; + tpfn += c.tp + c.fn; + total = c.total; + } - csv.printRecord("Info", "Value"); + OptionalDouble precision = counts.values().stream().mapToDouble(TripChoiceAnalysis::precision).average(); + OptionalDouble recall = counts.values().stream().mapToDouble(TripChoiceAnalysis::recall).average(); + OptionalDouble f1 = counts.values().stream().mapToDouble(TripChoiceAnalysis::f1).average(); - csv.printRecord("Accuracy", "TODO"); - csv.printRecord("F1 Score", "TODO"); -// csv.printRecord("AUC-ROC", ""); - csv.printRecord("Precision", "TODO"); - csv.printRecord("Recall", "TODO"); + try (CSVPrinter csv = new CSVPrinter(Files.newBufferedWriter(path), CSVFormat.DEFAULT)) { - // These can be micro and macro averaged + csv.printRecord("Info", "Value"); + csv.printRecord("Accuracy", tp / total); + csv.printRecord("Precision (micro avg.)", tp / tpfp); + csv.printRecord("Precision (macro avg.)", precision.orElse(0)); + csv.printRecord("Recall (micro avg.)", tp / tpfn); + csv.printRecord("Recall (macro avg.)", recall.orElse(0)); + csv.printRecord("F1 Score (micro avg.)", 2 * tp / (tpfp + tpfn)); + csv.printRecord("F1 Score (macro avg.)", f1.orElse(0)); } // TODO Cohen’s Kappa, Cross-Entropy, Mathews Correlation Coefficient (MCC) @@ -98,14 +153,23 @@ public void writeChoiceEvaluation(Path path) throws IOException { */ public void writeChoiceEvaluationPerMode(Path path) throws IOException { + // Precision in multi-class classification is the fraction of instances correctly classified as belonging to a specific class out of all instances the model predicted to belong to that class. + + // Recall in multi-class classification is the fraction of instances in a class that the model correctly classified out of all instances in that class. + try (CSVPrinter csv = new CSVPrinter(Files.newBufferedWriter(path), CSVFormat.DEFAULT)) { - csv.printRecord("Accuracy", "TODO"); - csv.printRecord("Precision", "TODO"); - csv.printRecord("Recall", "TODO"); - csv.printRecord("F1 Score", "TODO"); + csv.printRecord("Mode", "Precision", "Recall", "F1 Score"); + for (String m : modeOrder) { + csv.print(m); + Counts c = counts.get(m); + csv.print(precision(c)); + csv.print(recall(c)); + csv.print(f1(c)); + csv.println(); + } } } @@ -115,4 +179,17 @@ public void writeChoiceEvaluationPerMode(Path path) throws IOException { private record Entry(String person, double weight, int n, String trueMode, String predMode) { } + + /** + * Contains true positive, false positive, false negative and true negative counts. + * + * @param tp correctly predicted this class + * @param fp incorrectly predicted this class + * @param fn incorrectly predicted different class + * @param tn correctly predicated different class + */ + private record Counts(double tp, double fp, double fn, double tn, double total) { + + } + } From 8fdf7247126222d81da2955e956190eb99e539dd Mon Sep 17 00:00:00 2001 From: rakow Date: Tue, 11 Jun 2024 10:27:56 +0200 Subject: [PATCH 13/31] add choice metrics to trip dashboard --- .../analysis/population/TripAnalysis.java | 5 +- .../population/TripChoiceAnalysis.java | 75 ++++++++++- .../simwrapper/dashboard/TripDashboard.java | 120 ++++++++++++++---- .../simwrapper/dashboard/DashboardTests.java | 3 +- 4 files changed, 173 insertions(+), 30 deletions(-) diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java index 02c3fd676d8..928695d14d9 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java @@ -41,7 +41,8 @@ produces = { "mode_share.csv", "mode_share_per_dist.csv", "mode_users.csv", "trip_stats.csv", "mode_share_per_%s.csv", "population_trip_stats.csv", "trip_purposes_by_hour.csv", - "mode_choices.csv", "mode_choice_evaluation.csv", "mode_choice_evaluation_per_mode.csv" + "mode_choices.csv", "mode_choice_evaluation.csv", "mode_choice_evaluation_per_mode.csv", + "mode_confusion_matrix.csv", "mode_prediction_error.csv" } ) public class TripAnalysis implements MATSimAppCommand { @@ -226,6 +227,8 @@ public Integer call() throws Exception { choices.writeChoices(output.getPath("mode_choices.csv")); choices.writeChoiceEvaluation(output.getPath("mode_choice_evaluation.csv")); choices.writeChoiceEvaluationPerMode(output.getPath("mode_choice_evaluation_per_mode.csv")); + choices.writeConfusionMatrix(output.getPath("mode_confusion_matrix.csv")); + choices.writeModePredictionError(output.getPath("mode_prediction_error.csv")); } catch (RuntimeException e) { log.error("Error while analyzing mode choices", e); } diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java index 11bf934b524..c196fd9c2b9 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java @@ -1,5 +1,9 @@ package org.matsim.application.analysis.population; +import it.unimi.dsi.fastutil.doubles.DoubleArrayList; +import it.unimi.dsi.fastutil.doubles.DoubleList; +import it.unimi.dsi.fastutil.objects.Object2DoubleMap; +import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVPrinter; import org.apache.logging.log4j.LogManager; @@ -37,6 +41,11 @@ final class TripChoiceAnalysis { */ private final Map counts = new HashMap<>(); + /** + * Contains confusion matrix for each mode. + */ + private final List confusionMatrix = new ArrayList<>(); + public TripChoiceAnalysis(Table persons, Table trips, List modeOrder) { persons = persons.where(persons.stringColumn("ref_modes").isNotEqualTo("")); trips = new DataFrameJoiner(trips, "person").inner(persons); @@ -64,6 +73,25 @@ public TripChoiceAnalysis(Table persons, Table trips, List modeOrder) { for (String mode : modeOrder) { counts.put(mode, countPredictions(mode, data)); + DoubleArrayList preds = new DoubleArrayList(); + + for (String predMode : modeOrder) { + double c = 0; + for (Entry e : data) { + if (!e.trueMode.equals(mode)) + continue; + if (e.predMode.equals(predMode)) + c += e.weight; + } + preds.add(c); + } + + double sum = preds.doubleStream().sum(); + for (int i = 0; i < preds.size(); i++) { + preds.set(i, preds.getDouble(i) / sum); + } + + confusionMatrix.add(preds); } } @@ -145,7 +173,7 @@ public void writeChoiceEvaluation(Path path) throws IOException { csv.printRecord("F1 Score (macro avg.)", f1.orElse(0)); } - // TODO Cohen’s Kappa, Cross-Entropy, Mathews Correlation Coefficient (MCC) + // TODO Cohen’s Kappa, Mathews Correlation Coefficient (MCC) } /** @@ -173,13 +201,54 @@ public void writeChoiceEvaluationPerMode(Path path) throws IOException { } } - // TODO: write confusion matrix + /** + * Write confusion matrix. + */ + public void writeConfusionMatrix(Path path) throws IOException { + try (CSVPrinter csv = new CSVPrinter(Files.newBufferedWriter(path), CSVFormat.DEFAULT)) { + csv.print("True/Pred"); + for (String s : modeOrder) { + csv.print(s); + } + csv.println(); + + for (int i = 0; i < modeOrder.size(); i++) { + csv.print(modeOrder.get(i)); + DoubleList row = confusionMatrix.get(i); + for (int j = 0; j < row.size(); j++) { + csv.print(row.getDouble(j)); + } + csv.println(); + } + } + } + + public void writeModePredictionError(Path path) throws IOException { + + Object2DoubleMap counts = new Object2DoubleOpenHashMap<>(); + // inefficient, should not be used on large datasets + for (Entry e : data) { + counts.mergeDouble(new Pair(e.trueMode(), e.predMode()), e.weight(), Double::sum); + } - // TODO Class Prediction Error + try (CSVPrinter csv = new CSVPrinter(Files.newBufferedWriter(path), CSVFormat.DEFAULT)) { + csv.printRecord("true_mode", "predicted_mode", "count"); + for (String trueMode : modeOrder) { + for (String predMode : modeOrder) { + double c = counts.getDouble(new Pair(trueMode, predMode)); + if (c > 0) + csv.printRecord(trueMode, predMode, c); + } + } + } + } private record Entry(String person, double weight, int n, String trueMode, String predMode) { } + private record Pair(String trueMode, String predMode) { + } + /** * Contains true positive, false positive, false negative and true negative counts. * diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index 315943d8333..e492b02fc4e 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -5,6 +5,7 @@ import org.matsim.simwrapper.Header; import org.matsim.simwrapper.Layout; import org.matsim.simwrapper.viz.ColorScheme; +import org.matsim.simwrapper.viz.Heatmap; import org.matsim.simwrapper.viz.Plotly; import org.matsim.simwrapper.viz.Table; import tech.tablesaw.plotly.components.Axis; @@ -33,6 +34,8 @@ public class TripDashboard implements Dashboard { private String[] args; + private boolean choiceEvaluation; + /** * Default trip dashboard constructor. */ @@ -70,6 +73,15 @@ public TripDashboard withGroupedRefData(String groupedRefCsv, String... categori return this; } + /** + * Enable choice evaluation tab. This only produces valid data if choice reference data was set in the population. + * @see TripAnalysis#ATTR_REF_MODES + */ + public TripDashboard withChoiceEvaluation(boolean enable) { + this.choiceEvaluation = enable; + return this; + } + /** * Set argument that will be passed to the analysis script. See {@link TripAnalysis}. */ @@ -98,7 +110,7 @@ public void configure(Header header, Layout layout) { args[this.args.length + 1] = groupedRefCsv; } - Layout.Row first = layout.row("first"); + Layout.Row first = layout.row("first", header.title); first.el(Plotly.class, (viz, data) -> { viz.title = "Modal split"; @@ -150,7 +162,7 @@ public void configure(Header header, Layout layout) { } }); - layout.row("second") + layout.row("second", header.title) .el(Table.class, (viz, data) -> { viz.title = "Mode Statistics"; viz.description = "by main mode, over whole trip (including access & egress)"; @@ -188,7 +200,7 @@ public void configure(Header header, Layout layout) { }); - layout.row("third") + layout.row("third", header.title) .el(Table.class, (viz, data) -> { viz.title = "Population statistics"; viz.description = "over simulated persons (not scaled by sample size)"; @@ -221,7 +233,7 @@ public void configure(Header header, Layout layout) { }); - layout.row("departures").el(Plotly.class, (viz, data) -> { + layout.row("departures", header.title).el(Plotly.class, (viz, data) -> { viz.title = "Departures"; viz.description = "by hour and purpose"; @@ -240,7 +252,7 @@ public void configure(Header header, Layout layout) { }); - layout.row("arrivals").el(Plotly.class, (viz, data) -> { + layout.row("arrivals", header.title).el(Plotly.class, (viz, data) -> { viz.title = "Arrivals"; viz.description = "by hour and purpose"; @@ -260,31 +272,89 @@ public void configure(Header header, Layout layout) { }); if (groupedRefCsv != null) { + createGroupedTab(layout, args); + } - // age,economic_status,dist_group,main_mode,share - layout.row("facets").el(Plotly.class, (viz, data) -> { + if (choiceEvaluation) { + createChoiceTab(layout, args); + } - viz.title = "FACETS"; - viz.description = "by hour and purpose"; - viz.layout = tech.tablesaw.plotly.components.Layout.builder() - .xAxis(Axis.builder().title("dist_group").build()) - .yAxis(Axis.builder().title("sim_share").build()) - .barMode(tech.tablesaw.plotly.components.Layout.BarMode.STACK) - .build(); + } - // TODO: Still in testing - viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT).build(), - viz.addDataset(data.computeWithPlaceholder(TripAnalysis.class, "mode_share_per_%s.csv", "age")).mapping() - .facetCol("age") - .name("main_mode", ColorScheme.Spectral) - .x("dist_group") - .y("sim_share") - ); + private void createChoiceTab(Layout layout, String[] args) { - }); + // TODO: these explanation texts should be a separate box. - // TODO create the additional tab + layout.row("choice", "Mode Choice").el(Table.class, (viz, data) -> { + viz.title = "Choice Evaluation"; + viz.description = "Metrics for mode choice. The macro-average computes the metric independently for each class and then take the average (hence treating all classes equally). " + + "The micro-average will aggregate the contributions of all classes to compute the average metric."; + viz.showAllRows = true; + viz.dataset = data.compute(TripAnalysis.class, "mode_choice_evaluation.csv", args); + }); - } + layout.row("choice", "Mode Choice").el(Table.class, (viz, data) -> { + viz.title = "Choice Evaluation per Mode"; + viz.description = "Precision is the fraction of instances correctly classified as belonging to a specific class out of all instances the model predicted to belong to that class." + + " Recall is the fraction of instances in a class that the model correctly classified out of all instances in that class."; + viz.showAllRows = true; + viz.dataset = data.compute(TripAnalysis.class, "mode_choice_evaluation_per_mode.csv", args); + }); + + layout.row("choice-plots", "Mode Choice").el(Heatmap.class, (viz, data) -> { + viz.title = "Confusion Matrix"; + viz.description = "Confusion matrix for mode choice."; + viz.xAxisTitle = "Predicted"; + viz.yAxisTitle = "True"; + viz.dataset = data.compute(TripAnalysis.class, "mode_confusion_matrix.csv", args); + }); + + layout.row("choice-plots", "Mode Choice").el(Plotly.class, (viz, data) -> { + viz.title = "Mode Prediction Error"; + viz.description = "Plot showing the number of (mis)classified modes."; + + viz.layout = tech.tablesaw.plotly.components.Layout.builder() + .xAxis(Axis.builder().title("True mode").build()) + .yAxis(Axis.builder().title("Predicted mode count").build()) + .barMode(tech.tablesaw.plotly.components.Layout.BarMode.STACK) + .build(); + + Plotly.DataMapping ds = viz.addDataset(data.compute(TripAnalysis.class, "mode_prediction_error.csv", args)) + .mapping() + .x("true_mode") + .y("count") + .name("predicted_mode"); + + viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT).build(), ds); + }); } + + private void createGroupedTab(Layout layout, String[] args) { + + // age,economic_status,dist_group,main_mode,share + layout.row("facets", "By Groups").el(Plotly.class, (viz, data) -> { + + viz.title = "FACETS"; + viz.description = "by hour and purpose"; + viz.layout = tech.tablesaw.plotly.components.Layout.builder() + .xAxis(Axis.builder().title("dist_group").build()) + .yAxis(Axis.builder().title("sim_share").build()) + .barMode(tech.tablesaw.plotly.components.Layout.BarMode.STACK) + .build(); + + // TODO: Still in testing + viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT).build(), + viz.addDataset(data.computeWithPlaceholder(TripAnalysis.class, "mode_share_per_%s.csv", "age")).mapping() + .facetCol("age") + .name("main_mode", ColorScheme.Spectral) + .x("dist_group") + .y("sim_share") + ); + + }); + + // TODO create the additional tab + + } + } diff --git a/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java b/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java index 34a3a3a3a8c..5d326fd2d35 100644 --- a/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java +++ b/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java @@ -78,7 +78,8 @@ void tripRef() { Path out = Path.of(utils.getOutputDirectory(), "analysis", "population"); TripDashboard dashboard = new TripDashboard("mode_share_ref.csv", "mode_share_per_dist_ref.csv", "mode_users_ref.csv") - .withGroupedRefData("mode_share_per_group_dist_ref.csv"); + .withGroupedRefData("mode_share_per_group_dist_ref.csv") + .withChoiceEvaluation(true); run(dashboard); Assertions.assertThat(out) From 1df0dafd7feb8bef6d2c4771543b154ebdd2f8b2 Mon Sep 17 00:00:00 2001 From: rakow Date: Tue, 11 Jun 2024 10:37:37 +0200 Subject: [PATCH 14/31] hide tab if not needed --- .../matsim/simwrapper/dashboard/TripDashboard.java | 13 ++++++++----- .../matsim/simwrapper/dashboard/DashboardTests.java | 5 ++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index e492b02fc4e..c0b57557af3 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -110,7 +110,10 @@ public void configure(Header header, Layout layout) { args[this.args.length + 1] = groupedRefCsv; } - Layout.Row first = layout.row("first", header.title); + // A tab will only be present if one of the other tabs is used as well + String tab = (groupedRefCsv != null || choiceEvaluation) ? header.title : null; + + Layout.Row first = layout.row("first", tab); first.el(Plotly.class, (viz, data) -> { viz.title = "Modal split"; @@ -162,7 +165,7 @@ public void configure(Header header, Layout layout) { } }); - layout.row("second", header.title) + layout.row("second", tab) .el(Table.class, (viz, data) -> { viz.title = "Mode Statistics"; viz.description = "by main mode, over whole trip (including access & egress)"; @@ -200,7 +203,7 @@ public void configure(Header header, Layout layout) { }); - layout.row("third", header.title) + layout.row("third", tab) .el(Table.class, (viz, data) -> { viz.title = "Population statistics"; viz.description = "over simulated persons (not scaled by sample size)"; @@ -233,7 +236,7 @@ public void configure(Header header, Layout layout) { }); - layout.row("departures", header.title).el(Plotly.class, (viz, data) -> { + layout.row("departures", tab).el(Plotly.class, (viz, data) -> { viz.title = "Departures"; viz.description = "by hour and purpose"; @@ -252,7 +255,7 @@ public void configure(Header header, Layout layout) { }); - layout.row("arrivals", header.title).el(Plotly.class, (viz, data) -> { + layout.row("arrivals", tab).el(Plotly.class, (viz, data) -> { viz.title = "Arrivals"; viz.description = "by hour and purpose"; diff --git a/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java b/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java index 5d326fd2d35..0f5b273b809 100644 --- a/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java +++ b/contribs/simwrapper/src/test/java/org/matsim/simwrapper/dashboard/DashboardTests.java @@ -84,7 +84,10 @@ void tripRef() { run(dashboard); Assertions.assertThat(out) .isDirectoryContaining("glob:**trip_stats.csv") - .isDirectoryContaining("glob:**mode_share.csv"); + .isDirectoryContaining("glob:**mode_share.csv") + .isDirectoryContaining("glob:**mode_choices.csv") + .isDirectoryContaining("glob:**mode_choice_evaluation.csv") + .isDirectoryContaining("glob:**mode_confusion_matrix.csv"); } From 90660110107600880ccc8da323ca9b6809a81de4 Mon Sep 17 00:00:00 2001 From: rakow Date: Tue, 11 Jun 2024 14:20:58 +0200 Subject: [PATCH 15/31] add text box and round metrics --- .../population/TripChoiceAnalysis.java | 26 ++++++++------ .../simwrapper/dashboard/TripDashboard.java | 34 +++++++++++-------- .../org/matsim/simwrapper/viz/TextBlock.java | 6 +++- 3 files changed, 41 insertions(+), 25 deletions(-) diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java index c196fd9c2b9..7c249216cbf 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java @@ -13,6 +13,8 @@ import tech.tablesaw.joining.DataFrameJoiner; import java.io.IOException; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -95,6 +97,10 @@ public TripChoiceAnalysis(Table persons, Table trips, List modeOrder) { } } + private static double round(double d) { + return BigDecimal.valueOf(d).setScale(5, RoundingMode.HALF_UP).doubleValue(); + } + private static double precision(Counts c) { return c.tp / (c.tp + c.fp); } @@ -164,13 +170,13 @@ public void writeChoiceEvaluation(Path path) throws IOException { csv.printRecord("Info", "Value"); - csv.printRecord("Accuracy", tp / total); - csv.printRecord("Precision (micro avg.)", tp / tpfp); - csv.printRecord("Precision (macro avg.)", precision.orElse(0)); - csv.printRecord("Recall (micro avg.)", tp / tpfn); - csv.printRecord("Recall (macro avg.)", recall.orElse(0)); - csv.printRecord("F1 Score (micro avg.)", 2 * tp / (tpfp + tpfn)); - csv.printRecord("F1 Score (macro avg.)", f1.orElse(0)); + csv.printRecord("Accuracy", round(tp / total)); + csv.printRecord("Precision (micro avg.)", round(tp / tpfp)); + csv.printRecord("Precision (macro avg.)", round(precision.orElse(0))); + csv.printRecord("Recall (micro avg.)", round(tp / tpfn)); + csv.printRecord("Recall (macro avg.)", round(recall.orElse(0))); + csv.printRecord("F1 Score (micro avg.)", round(2 * tp / (tpfp + tpfn))); + csv.printRecord("F1 Score (macro avg.)", round(f1.orElse(0))); } // TODO Cohen’s Kappa, Mathews Correlation Coefficient (MCC) @@ -193,9 +199,9 @@ public void writeChoiceEvaluationPerMode(Path path) throws IOException { Counts c = counts.get(m); - csv.print(precision(c)); - csv.print(recall(c)); - csv.print(f1(c)); + csv.print(round(precision(c))); + csv.print(round(recall(c))); + csv.print(round(f1(c))); csv.println(); } } diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index c0b57557af3..caf839e1579 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -4,10 +4,7 @@ import org.matsim.simwrapper.Dashboard; import org.matsim.simwrapper.Header; import org.matsim.simwrapper.Layout; -import org.matsim.simwrapper.viz.ColorScheme; -import org.matsim.simwrapper.viz.Heatmap; -import org.matsim.simwrapper.viz.Plotly; -import org.matsim.simwrapper.viz.Table; +import org.matsim.simwrapper.viz.*; import tech.tablesaw.plotly.components.Axis; import tech.tablesaw.plotly.traces.BarTrace; @@ -59,8 +56,14 @@ public TripDashboard(@Nullable String modeShareRefCsv, @Nullable String modeShar args = new String[0]; } + private static String[] detectCategories(String groupedRefCsv) { + // TODO: Implement + return new String[0]; + } + /** * Set grouped reference data. Will enable additional tab with analysis for subgroups of the population. + * * @param groupedRefCsv resource containing the grouped reference data * @param categories categories to show on dashboard, if empty all categories will be used */ @@ -75,6 +78,7 @@ public TripDashboard withGroupedRefData(String groupedRefCsv, String... categori /** * Enable choice evaluation tab. This only produces valid data if choice reference data was set in the population. + * * @see TripAnalysis#ATTR_REF_MODES */ public TripDashboard withChoiceEvaluation(boolean enable) { @@ -90,11 +94,6 @@ public TripDashboard setAnalysisArgs(String... args) { return this; } - private static String[] detectCategories(String groupedRefCsv) { - // TODO: Implement - return new String[0]; - } - @Override public void configure(Header header, Layout layout) { @@ -286,20 +285,27 @@ public void configure(Header header, Layout layout) { private void createChoiceTab(Layout layout, String[] args) { - // TODO: these explanation texts should be a separate box. + layout.row("choice-intro", "Mode Choice").el(TextBlock.class, (viz, data) -> { + viz.title = "Introduction"; + viz.content = """ + Information regarding the metrics used: + + Precision is the fraction of instances correctly classified as belonging to a specific class out of all instances the model predicted to belong to that class. + Recall is the fraction of instances in a class that the model correctly classified out of all instances in that class. + The macro-average computes the metric independently for each class and then take the average (hence treating all classes equally). + The micro-average will aggregate the contributions of all classes to compute the average metric."""; + }); layout.row("choice", "Mode Choice").el(Table.class, (viz, data) -> { viz.title = "Choice Evaluation"; - viz.description = "Metrics for mode choice. The macro-average computes the metric independently for each class and then take the average (hence treating all classes equally). " + - "The micro-average will aggregate the contributions of all classes to compute the average metric."; + viz.description = "Metrics for mode choice."; viz.showAllRows = true; viz.dataset = data.compute(TripAnalysis.class, "mode_choice_evaluation.csv", args); }); layout.row("choice", "Mode Choice").el(Table.class, (viz, data) -> { viz.title = "Choice Evaluation per Mode"; - viz.description = "Precision is the fraction of instances correctly classified as belonging to a specific class out of all instances the model predicted to belong to that class." + - " Recall is the fraction of instances in a class that the model correctly classified out of all instances in that class."; + viz.description = "Metrics for mode choice per mode."; viz.showAllRows = true; viz.dataset = data.compute(TripAnalysis.class, "mode_choice_evaluation_per_mode.csv", args); }); diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/TextBlock.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/TextBlock.java index aaa21607c5c..e84ddc494b3 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/TextBlock.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/TextBlock.java @@ -10,9 +10,13 @@ public class TextBlock extends Viz { /** * The filepath containing the data. */ - @JsonProperty(required = true) public String file; + /** + * Content of the text block. Can be used instead of file to directly include content. + */ + public String content; + public TextBlock() { super("text"); } From a60bc3ec5dfe02cdccf4a463a1ea0b17ed8b373c Mon Sep 17 00:00:00 2001 From: rakow Date: Tue, 11 Jun 2024 17:46:20 +0200 Subject: [PATCH 16/31] normalize shares per reference group correctly, added some facet dashboards (WIP) --- .../analysis/population/TripAnalysis.java | 2 +- .../population/TripByGroupAnalysis.java | 16 +- .../population/TripChoiceAnalysis.java | 9 +- .../simwrapper/dashboard/TripDashboard.java | 69 +- .../mode_share_per_group_dist_ref.csv | 1502 ++++++++++++++--- 5 files changed, 1299 insertions(+), 299 deletions(-) diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java index 928695d14d9..ef2c70e726a 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java @@ -49,7 +49,7 @@ public class TripAnalysis implements MATSimAppCommand { private static final Logger log = LogManager.getLogger(TripAnalysis.class); /** - * Person attribute that contains the reference modes of a person. + * Person attribute that contains the reference modes of a person. Multiple modes are delimited by "-". */ public static String ATTR_REF_MODES = "ref_modes"; /** diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java index 59f669ad159..7a0787767cf 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java @@ -103,9 +103,19 @@ void analyzeModeShare(Table trips, List dists, Function ou Comparator cmp = Comparator.comparingInt(row -> dists.indexOf(row.getString("dist_group"))); aggr = aggr.sortOn(cmp.thenComparing(row -> row.getString("main_mode"))); - // TODO: norm by category and dist_group - // probably need two separate files as well (with and without dist) - // not normed is more useful for now + // Norm each group to 1 + String norm = group.columns.get(0); + if (group.columns.size() > 1) + throw new UnsupportedOperationException("Multiple columns not supported yet"); + + for (String label : aggr.stringColumn(norm).asSet()) { + DoubleColumn dist_group = aggr.doubleColumn("sim_share"); + Selection sel = aggr.stringColumn(norm).isEqualTo(label); + + double total = dist_group.where(sel).sum(); + if (total > 0) + dist_group.set(sel, dist_group.divide(total)); + } Table joined = new DataFrameJoiner(group.data, join).leftOuter(aggr); joined.column("share").setName("ref_share"); diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java index 7c249216cbf..4857359d854 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java @@ -98,6 +98,9 @@ public TripChoiceAnalysis(Table persons, Table trips, List modeOrder) { } private static double round(double d) { + if (Double.isNaN(d)) + return Double.NaN; + return BigDecimal.valueOf(d).setScale(5, RoundingMode.HALF_UP).doubleValue(); } @@ -162,9 +165,9 @@ public void writeChoiceEvaluation(Path path) throws IOException { total = c.total; } - OptionalDouble precision = counts.values().stream().mapToDouble(TripChoiceAnalysis::precision).average(); - OptionalDouble recall = counts.values().stream().mapToDouble(TripChoiceAnalysis::recall).average(); - OptionalDouble f1 = counts.values().stream().mapToDouble(TripChoiceAnalysis::f1).average(); + OptionalDouble precision = counts.values().stream().mapToDouble(TripChoiceAnalysis::precision).filter(Double::isFinite).average(); + OptionalDouble recall = counts.values().stream().mapToDouble(TripChoiceAnalysis::recall).filter(Double::isFinite).average(); + OptionalDouble f1 = counts.values().stream().mapToDouble(TripChoiceAnalysis::f1).filter(Double::isFinite).average(); try (CSVPrinter csv = new CSVPrinter(Files.newBufferedWriter(path), CSVFormat.DEFAULT)) { diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index caf839e1579..1ab7dee2244 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -1,22 +1,33 @@ package org.matsim.simwrapper.dashboard; import org.matsim.application.analysis.population.TripAnalysis; +import org.matsim.application.options.CsvOptions; +import org.matsim.core.utils.io.IOUtils; import org.matsim.simwrapper.Dashboard; import org.matsim.simwrapper.Header; import org.matsim.simwrapper.Layout; import org.matsim.simwrapper.viz.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import tech.tablesaw.plotly.components.Axis; import tech.tablesaw.plotly.traces.BarTrace; import javax.annotation.Nullable; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; /** * Shows trip information, optionally against reference data. */ public class TripDashboard implements Dashboard { + private static final Logger log = LoggerFactory.getLogger(TripDashboard.class); + @Nullable private final String modeShareRefCsv; @Nullable @@ -57,8 +68,18 @@ public TripDashboard(@Nullable String modeShareRefCsv, @Nullable String modeShar } private static String[] detectCategories(String groupedRefCsv) { - // TODO: Implement - return new String[0]; + try { + Character c = CsvOptions.detectDelimiter(groupedRefCsv); + try (BufferedReader reader = IOUtils.getBufferedReader(groupedRefCsv)) { + String header = reader.readLine(); + return Arrays.stream(header.split(String.valueOf(c))) + .filter(s -> !s.equals("main_mode") && !s.equals("share") && !s.equals("dist_group")) + .toArray(String[]::new); + } + + } catch (IOException e) { + throw new UncheckedIOException(e); + } } /** @@ -71,6 +92,7 @@ public TripDashboard withGroupedRefData(String groupedRefCsv, String... categori this.groupedRefCsv = groupedRefCsv; if (categories.length == 0) { categories = detectCategories(groupedRefCsv); + log.info("Detected categories from reference data: {}", Arrays.toString(categories)); } this.categories = categories; return this; @@ -340,30 +362,35 @@ private void createChoiceTab(Layout layout, String[] args) { private void createGroupedTab(Layout layout, String[] args) { - // age,economic_status,dist_group,main_mode,share - layout.row("facets", "By Groups").el(Plotly.class, (viz, data) -> { + for (String cat : Objects.requireNonNull(categories, "Categories not set")) { - viz.title = "FACETS"; - viz.description = "by hour and purpose"; - viz.layout = tech.tablesaw.plotly.components.Layout.builder() - .xAxis(Axis.builder().title("dist_group").build()) - .yAxis(Axis.builder().title("sim_share").build()) - .barMode(tech.tablesaw.plotly.components.Layout.BarMode.STACK) - .build(); + layout.row("category_" + cat, "By Groups").el(Plotly.class, (viz, data) -> { - // TODO: Still in testing - viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT).build(), - viz.addDataset(data.computeWithPlaceholder(TripAnalysis.class, "mode_share_per_%s.csv", "age")).mapping() - .facetCol("age") - .name("main_mode", ColorScheme.Spectral) - .x("dist_group") - .y("sim_share") - ); + viz.title = "Mode share"; + viz.description = "by " + cat; + viz.layout = tech.tablesaw.plotly.components.Layout.builder() + .xAxis(Axis.builder().title("dist_group").build()) + .yAxis(Axis.builder().title("sim_share").build()) + .barMode(tech.tablesaw.plotly.components.Layout.BarMode.STACK) + .build(); - }); + // TODO: Still in testing + Plotly.DataMapping ds = viz.addDataset(data.computeWithPlaceholder(TripAnalysis.class, "mode_share_per_%s.csv", cat)) + .pivot(List.of("main_mode"), "source", "share") + .aggregate(List.of("main_mode", "source", cat), "share", Plotly.AggrFunc.SUM) + .mapping() + .facetCol(cat) + .name("main_mode") + .x("share") + .y("source"); - // TODO create the additional tab + viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT) + .orientation(BarTrace.Orientation.HORIZONTAL) + .build(), ds); + }); + + } } } diff --git a/contribs/simwrapper/src/test/resources/mode_share_per_group_dist_ref.csv b/contribs/simwrapper/src/test/resources/mode_share_per_group_dist_ref.csv index 7ab70a67d9d..a293d24b2c2 100644 --- a/contribs/simwrapper/src/test/resources/mode_share_per_group_dist_ref.csv +++ b/contribs/simwrapper/src/test/resources/mode_share_per_group_dist_ref.csv @@ -1,271 +1,1231 @@ -age,economic_status,dist_group,main_mode,share -,,0 - 1000,bike,0.03993953574665 -,,0 - 1000,car,0.010077686113465548 -,,0 - 1000,pt,0.0035575461836548946 -,,0 - 1000,ride,0.0037969819009802397 -,,0 - 1000,walk,0.22881070677001764 -,,1000 - 2000,bike,0.04698342341346555 -,,1000 - 2000,car,0.023147292190076742 -,,1000 - 2000,pt,0.02007970854535886 -,,1000 - 2000,ride,0.010215459406613094 -,,1000 - 2000,walk,0.05220619945665914 -,,2000 - 5000,bike,0.05624516000252578 -,,2000 - 5000,car,0.05164117094781402 -,,2000 - 5000,pt,0.07035847284671681 -,,2000 - 5000,ride,0.019753111426262702 -,,2000 - 5000,walk,0.013501281662145714 -,,5000 - 10000,bike,0.02693069701653698 -,,5000 - 10000,car,0.05215320525856776 -,,5000 - 10000,pt,0.08244750747280563 -,,5000 - 10000,ride,0.01397122077983446 -,,5000 - 10000,walk,0.001517535520605139 -,,10000 - 20000,bike,0.007136782872703979 -,,10000 - 20000,car,0.04134445948649564 -,,10000 - 20000,pt,0.06385899348452925 -,,10000 - 20000,ride,0.007926042173942492 -,,10000 - 20000,walk,0.0005273153061550004 -,,20000+,bike,0.0006419443250597948 -,,20000+,car,0.022309302342807483 -,,20000+,pt,0.024771142628339887 -,,20000+,ride,0.003944497517436229 -,,20000+,walk,0.00020561720177352156 -0 - 18,,0 - 1000,bike,0.07229378725286068 -0 - 18,,0 - 1000,car,2.2787447948172964e-05 -0 - 18,,0 - 1000,pt,0.0032885333542058675 -0 - 18,,0 - 1000,ride,0.014726340770922693 -0 - 18,,0 - 1000,walk,0.27842606902732 -0 - 18,,1000 - 2000,bike,0.08569283527359176 -0 - 18,,1000 - 2000,car,9.9737820478634e-05 -0 - 18,,1000 - 2000,pt,0.02591594763943778 -0 - 18,,1000 - 2000,ride,0.0405848317671109 -0 - 18,,1000 - 2000,walk,0.06385876158993227 -0 - 18,,2000 - 5000,bike,0.06412457627408044 -0 - 18,,2000 - 5000,car,0.00013846416722995588 -0 - 18,,2000 - 5000,pt,0.10245573841407069 -0 - 18,,2000 - 5000,ride,0.0696453433344005 -0 - 18,,2000 - 5000,walk,0.008117395318933516 -0 - 18,,5000 - 10000,bike,0.007277279625603125 -0 - 18,,5000 - 10000,car,0.00013224825537415898 -0 - 18,,5000 - 10000,pt,0.07106402349407442 -0 - 18,,5000 - 10000,ride,0.03997163402508548 -0 - 18,,5000 - 10000,walk,0.0007362455953801282 -0 - 18,,10000 - 20000,bike,0.0005175567280619023 -0 - 18,,10000 - 20000,car,5.133475935144192e-05 -0 - 18,,10000 - 20000,pt,0.023911770521047097 -0 - 18,,10000 - 20000,ride,0.01566996165519817 -0 - 18,,10000 - 20000,walk,0.00027820875574264116 -0 - 18,,20000+,bike,0.00014888881816754997 -0 - 18,,20000+,car,0.0 -0 - 18,,20000+,pt,0.00541683946477697 -0 - 18,,20000+,ride,0.005078946237578967 -0 - 18,,20000+,walk,0.00035391261203410667 -18 - 66,,0 - 1000,bike,0.035843127186867024 -18 - 66,,0 - 1000,car,0.010335065645936097 -18 - 66,,0 - 1000,pt,0.0024306518695817693 -18 - 66,,0 - 1000,ride,0.0012660481672359296 -18 - 66,,0 - 1000,walk,0.2041146382136089 -18 - 66,,1000 - 2000,bike,0.043352649338563545 -18 - 66,,1000 - 2000,car,0.024096827714692407 -18 - 66,,1000 - 2000,pt,0.015542882507096583 -18 - 66,,1000 - 2000,ride,0.0035363974027805417 -18 - 66,,1000 - 2000,walk,0.045659194729557075 -18 - 66,,2000 - 5000,bike,0.0631398064777341 -18 - 66,,2000 - 5000,car,0.054818071432069675 -18 - 66,,2000 - 5000,pt,0.06482579238881256 -18 - 66,,2000 - 5000,ride,0.00832075054319505 -18 - 66,,2000 - 5000,walk,0.01333842505167683 -18 - 66,,5000 - 10000,bike,0.03637397303511378 -18 - 66,,5000 - 10000,car,0.060555551635949174 -18 - 66,,5000 - 10000,pt,0.09229605293671034 -18 - 66,,5000 - 10000,ride,0.00759953611299685 -18 - 66,,5000 - 10000,walk,0.001544428102195918 -18 - 66,,10000 - 20000,bike,0.009833559150348366 -18 - 66,,10000 - 20000,car,0.05152644339404211 -18 - 66,,10000 - 20000,pt,0.07923721033910733 -18 - 66,,10000 - 20000,ride,0.005266544462625669 -18 - 66,,10000 - 20000,walk,0.00047023586422664473 -18 - 66,,20000+,bike,0.0008439707548347554 -18 - 66,,20000+,car,0.028513445657508366 -18 - 66,,20000+,pt,0.032056642580330474 -18 - 66,,20000+,ride,0.0030548198681034284 -18 - 66,,20000+,walk,0.00020725743649875283 -66+,,0 - 1000,bike,0.028917781379748875 -66+,,0 - 1000,car,0.01756905891685044 -66+,,0 - 1000,pt,0.008273204993617672 -66+,,0 - 1000,ride,0.004628191849485549 -66+,,0 - 1000,walk,0.2851630822571291 -66+,,1000 - 2000,bike,0.02854964969272559 -66+,,1000 - 2000,car,0.03882086040651397 -66+,,1000 - 2000,pt,0.0331697357735842 -66+,,1000 - 2000,ride,0.011117551434265606 -66+,,1000 - 2000,walk,0.06845936405951983 -66+,,2000 - 5000,bike,0.022205536519422444 -66+,,2000 - 5000,car,0.08261732385978052 -66+,,2000 - 5000,pt,0.0650551591678631 -66+,,2000 - 5000,ride,0.023067556374726784 -66+,,2000 - 5000,walk,0.01871777257889785 -66+,,5000 - 10000,bike,0.0060053800450549635 -66+,,5000 - 10000,car,0.0627074379519613 -66+,,5000 - 10000,pt,0.05292597833638581 -66+,,5000 - 10000,ride,0.0172732713428096 -66+,,5000 - 10000,walk,0.0020729972598010397 -66+,,10000 - 20000,bike,0.0020118745335472974 -66+,,10000 - 20000,car,0.03570337079193992 -66+,,10000 - 20000,pt,0.03652161682334497 -66+,,10000 - 20000,ride,0.011929475838790412 -66+,,10000 - 20000,walk,0.0009657233478543096 -66+,,20000+,bike,0.00025574980638623377 -66+,,20000+,car,0.01651491485906698 -66+,,20000+,pt,0.012176960792435333 -66+,,20000+,ride,0.006529537524723665 -66+,,20000+,walk,7.388148176658781e-05 -,very_low,0 - 1000,bike,0.03470681238249625 -,very_low,0 - 1000,car,0.00664566014131732 -,very_low,0 - 1000,pt,0.00760869670923257 -,very_low,0 - 1000,ride,0.003124728592942877 -,very_low,0 - 1000,walk,0.24469895082287613 -,very_low,1000 - 2000,bike,0.04568124665405379 -,very_low,1000 - 2000,car,0.01762264405555989 -,very_low,1000 - 2000,pt,0.03176643067456601 -,very_low,1000 - 2000,ride,0.009316330422067991 -,very_low,1000 - 2000,walk,0.06872298007466462 -,very_low,2000 - 5000,bike,0.050899816256695075 -,very_low,2000 - 5000,car,0.033749602344892396 -,very_low,2000 - 5000,pt,0.09486902097108738 -,very_low,2000 - 5000,ride,0.015788943796908508 -,very_low,2000 - 5000,walk,0.017570734627488975 -,very_low,5000 - 10000,bike,0.02275352649147605 -,very_low,5000 - 10000,car,0.027723850662754297 -,very_low,5000 - 10000,pt,0.10839849171594336 -,very_low,5000 - 10000,ride,0.012009036032519245 -,very_low,5000 - 10000,walk,0.0015394033588941112 -,very_low,10000 - 20000,bike,0.004407524031423659 -,very_low,10000 - 20000,car,0.019387817923563564 -,very_low,10000 - 20000,pt,0.0734242401915033 -,very_low,10000 - 20000,ride,0.004587556923703756 -,very_low,10000 - 20000,walk,0.001209125736696242 -,very_low,20000+,bike,0.00041095332498231995 -,very_low,20000+,car,0.009550733296281531 -,very_low,20000+,pt,0.027400397002285904 -,very_low,20000+,ride,0.004042413867625509 -,very_low,20000+,walk,0.00038233091349725186 -,low,0 - 1000,bike,0.041246470043080165 -,low,0 - 1000,car,0.008910466432937744 -,low,0 - 1000,pt,0.004293903536684401 -,low,0 - 1000,ride,0.0033071684223682056 -,low,0 - 1000,walk,0.24947973724879838 -,low,1000 - 2000,bike,0.04961773460207695 -,low,1000 - 2000,car,0.019329953900366764 -,low,1000 - 2000,pt,0.025483245683043533 -,low,1000 - 2000,ride,0.009185948988387805 -,low,1000 - 2000,walk,0.05237722044190244 -,low,2000 - 5000,bike,0.052466234584333846 -,low,2000 - 5000,car,0.04278374195939727 -,low,2000 - 5000,pt,0.08432283560598662 -,low,2000 - 5000,ride,0.01702898827541261 -,low,2000 - 5000,walk,0.015885053481139822 -,low,5000 - 10000,bike,0.022672784003795167 -,low,5000 - 10000,car,0.04475208451328922 -,low,5000 - 10000,pt,0.09238030305339276 -,low,5000 - 10000,ride,0.012354387654785704 -,low,5000 - 10000,walk,0.0013482692004642123 -,low,10000 - 20000,bike,0.00606581254281994 -,low,10000 - 20000,car,0.031209843665855812 -,low,10000 - 20000,pt,0.0653262065900355 -,low,10000 - 20000,ride,0.006909609553827318 -,low,10000 - 20000,walk,0.0005263054577439737 -,low,20000+,bike,0.00047003197176413144 -,low,20000+,car,0.013483593316552238 -,low,20000+,pt,0.023496583359092254 -,low,20000+,ride,0.0032589486332435533 -,low,20000+,walk,2.6533277421522046e-05 -,medium,0 - 1000,bike,0.03911700063927027 -,medium,0 - 1000,car,0.011217731375356293 -,medium,0 - 1000,pt,0.0036674145395910263 -,medium,0 - 1000,ride,0.0040009596846765074 -,medium,0 - 1000,walk,0.2331501383860879 -,medium,1000 - 2000,bike,0.04558521294999408 -,medium,1000 - 2000,car,0.02544280536333108 -,medium,1000 - 2000,pt,0.019434896147692903 -,medium,1000 - 2000,ride,0.009855997679676024 -,medium,1000 - 2000,walk,0.0537914970446092 -,medium,2000 - 5000,bike,0.05473092890667934 -,medium,2000 - 5000,car,0.05384396289524313 -,medium,2000 - 5000,pt,0.06617655580215061 -,medium,2000 - 5000,ride,0.01916203987731777 -,medium,2000 - 5000,walk,0.013040227239873697 -,medium,5000 - 10000,bike,0.02574636249329226 -,medium,5000 - 10000,car,0.05641048735516864 -,medium,5000 - 10000,pt,0.08034283586815048 -,medium,5000 - 10000,ride,0.01428950786852113 -,medium,5000 - 10000,walk,0.0018418487953256962 -,medium,10000 - 20000,bike,0.006346523947575605 -,medium,10000 - 20000,car,0.04150377161860774 -,medium,10000 - 20000,pt,0.0629525618466731 -,medium,10000 - 20000,ride,0.0074802925048469545 -,medium,10000 - 20000,walk,0.0003904121475787536 -,medium,20000+,bike,0.000680433218446468 -,medium,20000+,car,0.021857757194240046 -,medium,20000+,pt,0.023834126105462355 -,medium,20000+,ride,0.003906827817956772 -,medium,20000+,walk,0.00019888268660406687 -,high,0 - 1000,bike,0.041939214472666166 -,high,0 - 1000,car,0.009296976205872368 -,high,0 - 1000,pt,0.0019245320576424141 -,high,0 - 1000,ride,0.0029544349795922243 -,high,0 - 1000,walk,0.21013293332150743 -,high,1000 - 2000,bike,0.046923469208975645 -,high,1000 - 2000,car,0.022359163419126016 -,high,1000 - 2000,pt,0.01598105459224138 -,high,1000 - 2000,ride,0.00969781710878335 -,high,1000 - 2000,walk,0.04629924755619724 -,high,2000 - 5000,bike,0.06385560203538197 -,high,2000 - 5000,car,0.05664195116716653 -,high,2000 - 5000,pt,0.06341010532730178 -,high,2000 - 5000,ride,0.02062977816711411 -,high,2000 - 5000,walk,0.013022335460471058 -,high,5000 - 10000,bike,0.03340249625957814 -,high,5000 - 10000,car,0.05522082435189495 -,high,5000 - 10000,pt,0.0768163134346059 -,high,5000 - 10000,ride,0.014151567281204704 -,high,5000 - 10000,walk,0.0010048399305621642 -,high,10000 - 20000,bike,0.009283542374603758 -,high,10000 - 20000,car,0.05082160352876232 -,high,10000 - 20000,pt,0.06361373744580245 -,high,10000 - 20000,ride,0.009672528719534406 -,high,10000 - 20000,walk,0.000206579718736878 -,high,20000+,bike,0.00043427613503130015 -,high,20000+,car,0.0302730459828126 -,high,20000+,pt,0.025319864147407976 -,high,20000+,ride,0.004383208486815366 -,high,20000+,walk,0.00032695712260737283 -,very_high,0 - 1000,bike,0.04115639089698632 -,very_high,0 - 1000,car,0.012722254325776056 -,very_high,0 - 1000,pt,0.0021241404664278096 -,very_high,0 - 1000,ride,0.006479698270689627 -,very_high,0 - 1000,walk,0.21073962672422653 -,very_high,1000 - 2000,bike,0.04962428972352181 -,very_high,1000 - 2000,car,0.02750613621612044 -,very_high,1000 - 2000,pt,0.013034837734497939 -,very_high,1000 - 2000,ride,0.015305658534843499 -,very_high,1000 - 2000,walk,0.044574423999390184 -,very_high,2000 - 5000,bike,0.054368817956349784 -,very_high,2000 - 5000,car,0.06181110351680053 -,very_high,2000 - 5000,pt,0.05826964813749158 -,very_high,2000 - 5000,ride,0.02786841949326947 -,very_high,2000 - 5000,walk,0.008853536562096663 -,very_high,5000 - 10000,bike,0.02620750926316137 -,very_high,5000 - 10000,car,0.06319670192405069 -,very_high,5000 - 10000,pt,0.06408662985856173 -,very_high,5000 - 10000,ride,0.016694136150129876 -,very_high,5000 - 10000,walk,0.0017755406053744846 -,very_high,10000 - 20000,bike,0.00916609078166196 -,very_high,10000 - 20000,car,0.05422199392062498 -,very_high,10000 - 20000,pt,0.05651365669095907 -,very_high,10000 - 20000,ride,0.010116960000757621 -,very_high,10000 - 20000,walk,0.0011846243905422437 -,very_high,20000+,bike,0.001489759414370527 -,very_high,20000+,car,0.03041067782980122 -,very_high,20000+,pt,0.026469066188086976 -,very_high,20000+,ride,0.003984235726180184 -,very_high,20000+,walk,4.343469724887364e-05 +age,income,economic_status,employment,car_avail,bike_avail,pt_abo_avail,dist_group,main_mode,share +,,,,,,,0 - 1000,bike,0.03993953574665 +,,,,,,,0 - 1000,car,0.010077686113465548 +,,,,,,,0 - 1000,pt,0.0035575461836548946 +,,,,,,,0 - 1000,ride,0.0037969819009802397 +,,,,,,,0 - 1000,walk,0.22881070677001764 +,,,,,,,1000 - 2000,bike,0.04698342341346555 +,,,,,,,1000 - 2000,car,0.023147292190076742 +,,,,,,,1000 - 2000,pt,0.02007970854535886 +,,,,,,,1000 - 2000,ride,0.010215459406613094 +,,,,,,,1000 - 2000,walk,0.05220619945665914 +,,,,,,,2000 - 5000,bike,0.05624516000252578 +,,,,,,,2000 - 5000,car,0.05164117094781402 +,,,,,,,2000 - 5000,pt,0.07035847284671681 +,,,,,,,2000 - 5000,ride,0.019753111426262702 +,,,,,,,2000 - 5000,walk,0.013501281662145714 +,,,,,,,5000 - 10000,bike,0.02693069701653698 +,,,,,,,5000 - 10000,car,0.05215320525856776 +,,,,,,,5000 - 10000,pt,0.08244750747280563 +,,,,,,,5000 - 10000,ride,0.01397122077983446 +,,,,,,,5000 - 10000,walk,0.001517535520605139 +,,,,,,,10000 - 20000,bike,0.007136782872703979 +,,,,,,,10000 - 20000,car,0.04134445948649564 +,,,,,,,10000 - 20000,pt,0.06385899348452925 +,,,,,,,10000 - 20000,ride,0.007926042173942492 +,,,,,,,10000 - 20000,walk,0.0005273153061550004 +,,,,,,,20000+,bike,0.0006419443250597948 +,,,,,,,20000+,car,0.022309302342807483 +,,,,,,,20000+,pt,0.024771142628339887 +,,,,,,,20000+,ride,0.003944497517436229 +,,,,,,,20000+,walk,0.00020561720177352156 +0 - 12,,,,,,,0 - 1000,bike,0.08835438432558193 +0 - 12,,,,,,,0 - 1000,car,3.432121636877185e-05 +0 - 12,,,,,,,0 - 1000,pt,0.0021749863020563654 +0 - 12,,,,,,,0 - 1000,ride,0.021051200382818012 +0 - 12,,,,,,,0 - 1000,walk,0.3432142504695775 +0 - 12,,,,,,,1000 - 2000,bike,0.08398330004325194 +0 - 12,,,,,,,1000 - 2000,car,0.000150219687811569 +0 - 12,,,,,,,1000 - 2000,pt,0.02306959433284934 +0 - 12,,,,,,,1000 - 2000,ride,0.05686810841448048 +0 - 12,,,,,,,1000 - 2000,walk,0.07347412145528517 +0 - 12,,,,,,,2000 - 5000,bike,0.04222757075895274 +0 - 12,,,,,,,2000 - 5000,car,6.536048317715672e-05 +0 - 12,,,,,,,2000 - 5000,pt,0.05396978017600571 +0 - 12,,,,,,,2000 - 5000,ride,0.08856585531656834 +0 - 12,,,,,,,2000 - 5000,walk,0.008408652825927461 +0 - 12,,,,,,,5000 - 10000,bike,0.0038947116326784196 +0 - 12,,,,,,,5000 - 10000,car,5.599841436553611e-05 +0 - 12,,,,,,,5000 - 10000,pt,0.02794477835232046 +0 - 12,,,,,,,5000 - 10000,ride,0.04558406298643193 +0 - 12,,,,,,,5000 - 10000,walk,0.0009301529133672708 +0 - 12,,,,,,,10000 - 20000,bike,7.862395528391248e-05 +0 - 12,,,,,,,10000 - 20000,car,0.0 +0 - 12,,,,,,,10000 - 20000,pt,0.007770578530816594 +0 - 12,,,,,,,10000 - 20000,ride,0.019094169122247644 +0 - 12,,,,,,,10000 - 20000,walk,0.0004190229166182497 +0 - 12,,,,,,,20000+,bike,3.432121636877185e-05 +0 - 12,,,,,,,20000+,car,0.0 +0 - 12,,,,,,,20000+,pt,0.0031358547205645646 +0 - 12,,,,,,,20000+,ride,0.00513600585361295 +0 - 12,,,,,,,20000+,walk,0.00031001319461127424 +12 - 18,,,,,,,0 - 1000,bike,0.04056261263021194 +12 - 18,,,,,,,0 - 1000,car,0.0 +12 - 18,,,,,,,0 - 1000,pt,0.00548858580569948 +12 - 18,,,,,,,0 - 1000,ride,0.0022302160066904092 +12 - 18,,,,,,,0 - 1000,walk,0.15042303841024 +12 - 18,,,,,,,1000 - 2000,bike,0.089070390956725 +12 - 18,,,,,,,1000 - 2000,car,0.0 +12 - 18,,,,,,,1000 - 2000,pt,0.03153953270294178 +12 - 18,,,,,,,1000 - 2000,ride,0.008413705601187106 +12 - 18,,,,,,,1000 - 2000,walk,0.044861543643076415 +12 - 18,,,,,,,2000 - 5000,bike,0.10738683499133322 +12 - 18,,,,,,,2000 - 5000,car,0.0002828962673402461 +12 - 18,,,,,,,2000 - 5000,pt,0.19825020985446604 +12 - 18,,,,,,,2000 - 5000,ride,0.032263789775858934 +12 - 18,,,,,,,2000 - 5000,walk,0.007541953274485525 +12 - 18,,,,,,,5000 - 10000,bike,0.013960272491340344 +12 - 18,,,,,,,5000 - 10000,car,0.0002828962673402461 +12 - 18,,,,,,,5000 - 10000,pt,0.15625539533377433 +12 - 18,,,,,,,5000 - 10000,ride,0.02888306971933014 +12 - 18,,,,,,,5000 - 10000,walk,0.00035313985281031226 +12 - 18,,,,,,,10000 - 20000,bike,0.0013847631212960674 +12 - 18,,,,,,,10000 - 20000,car,0.0001527576520257711 +12 - 18,,,,,,,10000 - 20000,pt,0.055802177791747724 +12 - 18,,,,,,,10000 - 20000,ride,0.008904701024528816 +12 - 18,,,,,,,10000 - 20000,walk,0.0 +12 - 18,,,,,,,20000+,bike,0.00037524183370703695 +12 - 18,,,,,,,20000+,car,0.0 +12 - 18,,,,,,,20000+,pt,0.009923416953924949 +12 - 18,,,,,,,20000+,ride,0.004966212655379154 +12 - 18,,,,,,,20000+,walk,0.000440645382538785 +18 - 25,,,,,,,0 - 1000,bike,0.02398561235781499 +18 - 25,,,,,,,0 - 1000,car,0.0048751521373352445 +18 - 25,,,,,,,0 - 1000,pt,0.003945859978488251 +18 - 25,,,,,,,0 - 1000,ride,0.0020741803522305053 +18 - 25,,,,,,,0 - 1000,walk,0.1351326848167619 +18 - 25,,,,,,,1000 - 2000,bike,0.03396527152384182 +18 - 25,,,,,,,1000 - 2000,car,0.009995183869916416 +18 - 25,,,,,,,1000 - 2000,pt,0.024513641618001722 +18 - 25,,,,,,,1000 - 2000,ride,0.0026681998585366875 +18 - 25,,,,,,,1000 - 2000,walk,0.0454726308118391 +18 - 25,,,,,,,2000 - 5000,bike,0.06318107428470454 +18 - 25,,,,,,,2000 - 5000,car,0.023251875706620243 +18 - 25,,,,,,,2000 - 5000,pt,0.10942869085194808 +18 - 25,,,,,,,2000 - 5000,ride,0.010494962098131744 +18 - 25,,,,,,,2000 - 5000,walk,0.015841120495424313 +18 - 25,,,,,,,5000 - 10000,bike,0.024391958956895745 +18 - 25,,,,,,,5000 - 10000,car,0.0305448384287256 +18 - 25,,,,,,,5000 - 10000,pt,0.16886134724827598 +18 - 25,,,,,,,5000 - 10000,ride,0.012529534856484127 +18 - 25,,,,,,,5000 - 10000,walk,0.001335058529996402 +18 - 25,,,,,,,10000 - 20000,bike,0.002283609567549982 +18 - 25,,,,,,,10000 - 20000,car,0.02354347014171086 +18 - 25,,,,,,,10000 - 20000,pt,0.13983024203024946 +18 - 25,,,,,,,10000 - 20000,ride,0.009738847778169043 +18 - 25,,,,,,,10000 - 20000,walk,0.00046040347416503706 +18 - 25,,,,,,,20000+,bike,0.0003598674743239825 +18 - 25,,,,,,,20000+,car,0.019844536404905074 +18 - 25,,,,,,,20000+,pt,0.05191307482568158 +18 - 25,,,,,,,20000+,ride,0.0054414707774460185 +18 - 25,,,,,,,20000+,walk,9.559874382553337e-05 +25 - 35,,,,,,,0 - 1000,bike,0.02710671281214892 +25 - 35,,,,,,,0 - 1000,car,0.0057994039547034845 +25 - 35,,,,,,,0 - 1000,pt,0.001279341348825648 +25 - 35,,,,,,,0 - 1000,ride,0.0009365409275199195 +25 - 35,,,,,,,0 - 1000,walk,0.22308917916513984 +25 - 35,,,,,,,1000 - 2000,bike,0.038879288211573826 +25 - 35,,,,,,,1000 - 2000,car,0.015574030733198638 +25 - 35,,,,,,,1000 - 2000,pt,0.014062407046561295 +25 - 35,,,,,,,1000 - 2000,ride,0.0027015671389454218 +25 - 35,,,,,,,1000 - 2000,walk,0.05276857867693636 +25 - 35,,,,,,,2000 - 5000,bike,0.06756351305973912 +25 - 35,,,,,,,2000 - 5000,car,0.0341919427125718 +25 - 35,,,,,,,2000 - 5000,pt,0.07993094118194079 +25 - 35,,,,,,,2000 - 5000,ride,0.006927040925022773 +25 - 35,,,,,,,2000 - 5000,walk,0.014527745750548082 +25 - 35,,,,,,,5000 - 10000,bike,0.04519783227041666 +25 - 35,,,,,,,5000 - 10000,car,0.04092311464281495 +25 - 35,,,,,,,5000 - 10000,pt,0.11711303425909293 +25 - 35,,,,,,,5000 - 10000,ride,0.007459596516540125 +25 - 35,,,,,,,5000 - 10000,walk,0.0011933932411686548 +25 - 35,,,,,,,10000 - 20000,bike,0.010371145878011873 +25 - 35,,,,,,,10000 - 20000,car,0.037122925967374674 +25 - 35,,,,,,,10000 - 20000,pt,0.0830885319675897 +25 - 35,,,,,,,10000 - 20000,ride,0.00427342214921809 +25 - 35,,,,,,,10000 - 20000,walk,0.00046959104556907043 +25 - 35,,,,,,,20000+,bike,0.0008650285684158126 +25 - 35,,,,,,,20000+,car,0.022531312078687672 +25 - 35,,,,,,,20000+,pt,0.04081677551162561 +25 - 35,,,,,,,20000+,ride,0.003080042714773812 +25 - 35,,,,,,,20000+,walk,0.00015601954332448127 +35 - 66,,,,,,,0 - 1000,bike,0.040332771508063456 +35 - 66,,,,,,,0 - 1000,car,0.01257661515762158 +35 - 66,,,,,,,0 - 1000,pt,0.002627358555652047 +35 - 66,,,,,,,0 - 1000,ride,0.001274292496410711 +35 - 66,,,,,,,0 - 1000,walk,0.20652019924881693 +35 - 66,,,,,,,1000 - 2000,bike,0.046077012040347726 +35 - 66,,,,,,,1000 - 2000,car,0.028801869123512337 +35 - 66,,,,,,,1000 - 2000,pt,0.014894709162723204 +35 - 66,,,,,,,1000 - 2000,ride,0.00393142725073833 +35 - 66,,,,,,,1000 - 2000,walk,0.04326782662357003 +35 - 66,,,,,,,2000 - 5000,bike,0.061631621878577604 +35 - 66,,,,,,,2000 - 5000,car,0.06587612217476327 +35 - 66,,,,,,,2000 - 5000,pt,0.053970521291472305 +35 - 66,,,,,,,2000 - 5000,ride,0.008515244305250143 +35 - 66,,,,,,,2000 - 5000,walk,0.012613220763112581 +35 - 66,,,,,,,5000 - 10000,bike,0.034913749435674296 +35 - 66,,,,,,,5000 - 10000,car,0.07107640811792384 +35 - 66,,,,,,,5000 - 10000,pt,0.07403986112593337 +35 - 66,,,,,,,5000 - 10000,ride,0.0070144527175531445 +35 - 66,,,,,,,5000 - 10000,walk,0.0016905536760423883 +35 - 66,,,,,,,10000 - 20000,bike,0.010619744339555919 +35 - 66,,,,,,,10000 - 20000,car,0.06001064856690114 +35 - 66,,,,,,,10000 - 20000,pt,0.07015337993949035 +35 - 66,,,,,,,10000 - 20000,ride,0.0050300495618203845 +35 - 66,,,,,,,10000 - 20000,walk,0.0004717166408375984 +35 - 66,,,,,,,20000+,bike,0.0008989376281988117 +35 - 66,,,,,,,20000+,car,0.03165819643378093 +35 - 66,,,,,,,20000+,pt,0.02653250556644344 +35 - 66,,,,,,,20000+,ride,0.0027399916814560116 +35 - 66,,,,,,,20000+,walk,0.00023899298775612217 +66+,,,,,,,0 - 1000,bike,0.028917781379748875 +66+,,,,,,,0 - 1000,car,0.01756905891685044 +66+,,,,,,,0 - 1000,pt,0.008273204993617672 +66+,,,,,,,0 - 1000,ride,0.004628191849485549 +66+,,,,,,,0 - 1000,walk,0.2851630822571291 +66+,,,,,,,1000 - 2000,bike,0.02854964969272559 +66+,,,,,,,1000 - 2000,car,0.03882086040651397 +66+,,,,,,,1000 - 2000,pt,0.0331697357735842 +66+,,,,,,,1000 - 2000,ride,0.011117551434265606 +66+,,,,,,,1000 - 2000,walk,0.06845936405951983 +66+,,,,,,,2000 - 5000,bike,0.022205536519422444 +66+,,,,,,,2000 - 5000,car,0.08261732385978052 +66+,,,,,,,2000 - 5000,pt,0.0650551591678631 +66+,,,,,,,2000 - 5000,ride,0.023067556374726784 +66+,,,,,,,2000 - 5000,walk,0.01871777257889785 +66+,,,,,,,5000 - 10000,bike,0.0060053800450549635 +66+,,,,,,,5000 - 10000,car,0.0627074379519613 +66+,,,,,,,5000 - 10000,pt,0.05292597833638581 +66+,,,,,,,5000 - 10000,ride,0.0172732713428096 +66+,,,,,,,5000 - 10000,walk,0.0020729972598010397 +66+,,,,,,,10000 - 20000,bike,0.0020118745335472974 +66+,,,,,,,10000 - 20000,car,0.03570337079193992 +66+,,,,,,,10000 - 20000,pt,0.03652161682334497 +66+,,,,,,,10000 - 20000,ride,0.011929475838790412 +66+,,,,,,,10000 - 20000,walk,0.0009657233478543096 +66+,,,,,,,20000+,bike,0.00025574980638623377 +66+,,,,,,,20000+,car,0.01651491485906698 +66+,,,,,,,20000+,pt,0.012176960792435333 +66+,,,,,,,20000+,ride,0.006529537524723665 +66+,,,,,,,20000+,walk,7.388148176658781e-05 +,0 - 500,,,,,,0 - 1000,bike,0.03669177865113303 +,0 - 500,,,,,,0 - 1000,car,0.00738398850586487 +,0 - 500,,,,,,0 - 1000,pt,0.007978210296398438 +,0 - 500,,,,,,0 - 1000,ride,0.0011450141882909117 +,0 - 500,,,,,,0 - 1000,walk,0.21475506048990617 +,0 - 500,,,,,,1000 - 2000,bike,0.05686516881650415 +,0 - 500,,,,,,1000 - 2000,car,0.014437527855602178 +,0 - 500,,,,,,1000 - 2000,pt,0.0390932425009502 +,0 - 500,,,,,,1000 - 2000,ride,0.0008025942354514118 +,0 - 500,,,,,,1000 - 2000,walk,0.0520912286100064 +,0 - 500,,,,,,2000 - 5000,bike,0.061478178043115554 +,0 - 500,,,,,,2000 - 5000,car,0.01709259019057143 +,0 - 500,,,,,,2000 - 5000,pt,0.10179655836178256 +,0 - 500,,,,,,2000 - 5000,ride,0.005808192545811038 +,0 - 500,,,,,,2000 - 5000,walk,0.004899508326691209 +,0 - 500,,,,,,5000 - 10000,bike,0.03557171175235263 +,0 - 500,,,,,,5000 - 10000,car,0.007644922733794155 +,0 - 500,,,,,,5000 - 10000,pt,0.12150867967192143 +,0 - 500,,,,,,5000 - 10000,ride,0.011819540955771745 +,0 - 500,,,,,,5000 - 10000,walk,0.0 +,0 - 500,,,,,,10000 - 20000,bike,0.0 +,0 - 500,,,,,,10000 - 20000,car,0.011056434745182113 +,0 - 500,,,,,,10000 - 20000,pt,0.10618540699650501 +,0 - 500,,,,,,10000 - 20000,ride,0.008832868383971678 +,0 - 500,,,,,,10000 - 20000,walk,0.0053587149815805465 +,0 - 500,,,,,,20000+,bike,0.0 +,0 - 500,,,,,,20000+,car,0.020473617216577353 +,0 - 500,,,,,,20000+,pt,0.036936597273339124 +,0 - 500,,,,,,20000+,ride,0.012292663670924705 +,0 - 500,,,,,,20000+,walk,0.0 +,500 - 900,,,,,,0 - 1000,bike,0.032856330403264675 +,500 - 900,,,,,,0 - 1000,car,0.005654343128338344 +,500 - 900,,,,,,0 - 1000,pt,0.013455806601091204 +,500 - 900,,,,,,0 - 1000,ride,0.0014720430155450257 +,500 - 900,,,,,,0 - 1000,walk,0.24849003889804464 +,500 - 900,,,,,,1000 - 2000,bike,0.04264617334968981 +,500 - 900,,,,,,1000 - 2000,car,0.0185348582476192 +,500 - 900,,,,,,1000 - 2000,pt,0.029751251528059353 +,500 - 900,,,,,,1000 - 2000,ride,0.005996648673162164 +,500 - 900,,,,,,1000 - 2000,walk,0.08238910591968335 +,500 - 900,,,,,,2000 - 5000,bike,0.05148933107546342 +,500 - 900,,,,,,2000 - 5000,car,0.024086347101453732 +,500 - 900,,,,,,2000 - 5000,pt,0.1037671430311061 +,500 - 900,,,,,,2000 - 5000,ride,0.009941756923120151 +,500 - 900,,,,,,2000 - 5000,walk,0.02781089395445394 +,500 - 900,,,,,,5000 - 10000,bike,0.024443567475169733 +,500 - 900,,,,,,5000 - 10000,car,0.021823598208739102 +,500 - 900,,,,,,5000 - 10000,pt,0.11481850923831592 +,500 - 900,,,,,,5000 - 10000,ride,0.0044810774712191965 +,500 - 900,,,,,,5000 - 10000,walk,0.001529902069657657 +,500 - 900,,,,,,10000 - 20000,bike,0.0057857733756045155 +,500 - 900,,,,,,10000 - 20000,car,0.013083644583328781 +,500 - 900,,,,,,10000 - 20000,pt,0.07851252715724369 +,500 - 900,,,,,,10000 - 20000,ride,0.002643755866810356 +,500 - 900,,,,,,10000 - 20000,walk,0.0010612155483804289 +,500 - 900,,,,,,20000+,bike,0.000509961763307295 +,500 - 900,,,,,,20000+,car,0.0028259746070602053 +,500 - 900,,,,,,20000+,pt,0.029137341215747518 +,500 - 900,,,,,,20000+,ride,0.001001079569320493 +,500 - 900,,,,,,20000+,walk,0.0 +,900 - 1500,,,,,,0 - 1000,bike,0.03598849459141938 +,900 - 1500,,,,,,0 - 1000,car,0.008086997634789332 +,900 - 1500,,,,,,0 - 1000,pt,0.005298771702446347 +,900 - 1500,,,,,,0 - 1000,ride,0.001953834330467959 +,900 - 1500,,,,,,0 - 1000,walk,0.2657550676806446 +,900 - 1500,,,,,,1000 - 2000,bike,0.04060526316240379 +,900 - 1500,,,,,,1000 - 2000,car,0.018126612379569047 +,900 - 1500,,,,,,1000 - 2000,pt,0.03098071071792005 +,900 - 1500,,,,,,1000 - 2000,ride,0.005657519926746878 +,900 - 1500,,,,,,1000 - 2000,walk,0.05276727722039848 +,900 - 1500,,,,,,2000 - 5000,bike,0.04830211278294567 +,900 - 1500,,,,,,2000 - 5000,car,0.04001310968653559 +,900 - 1500,,,,,,2000 - 5000,pt,0.09014630107046426 +,900 - 1500,,,,,,2000 - 5000,ride,0.009713854846172548 +,900 - 1500,,,,,,2000 - 5000,walk,0.016648527622471675 +,900 - 1500,,,,,,5000 - 10000,bike,0.025427948492362438 +,900 - 1500,,,,,,5000 - 10000,car,0.04210363990701105 +,900 - 1500,,,,,,5000 - 10000,pt,0.1042034227160141 +,900 - 1500,,,,,,5000 - 10000,ride,0.007633625687591608 +,900 - 1500,,,,,,5000 - 10000,walk,0.0020922113653862396 +,900 - 1500,,,,,,10000 - 20000,bike,0.005488321233178787 +,900 - 1500,,,,,,10000 - 20000,car,0.028042448389659042 +,900 - 1500,,,,,,10000 - 20000,pt,0.0686150844425172 +,900 - 1500,,,,,,10000 - 20000,ride,0.004897732846476809 +,900 - 1500,,,,,,10000 - 20000,walk,0.00037570368405910224 +,900 - 1500,,,,,,20000+,bike,0.0005080049357036462 +,900 - 1500,,,,,,20000+,car,0.010660487225736238 +,900 - 1500,,,,,,20000+,pt,0.02715034948058022 +,900 - 1500,,,,,,20000+,ride,0.002383258599110134 +,900 - 1500,,,,,,20000+,walk,0.00037330563921799005 +,1500 - 2000,,,,,,0 - 1000,bike,0.031995950241218714 +,1500 - 2000,,,,,,0 - 1000,car,0.008687841197317943 +,1500 - 2000,,,,,,0 - 1000,pt,0.00499336506717366 +,1500 - 2000,,,,,,0 - 1000,ride,0.0023206260617257162 +,1500 - 2000,,,,,,0 - 1000,walk,0.24159595914041004 +,1500 - 2000,,,,,,1000 - 2000,bike,0.0411144158645314 +,1500 - 2000,,,,,,1000 - 2000,car,0.02349176574057982 +,1500 - 2000,,,,,,1000 - 2000,pt,0.02604275793509365 +,1500 - 2000,,,,,,1000 - 2000,ride,0.007016076224911017 +,1500 - 2000,,,,,,1000 - 2000,walk,0.06013562478859629 +,1500 - 2000,,,,,,2000 - 5000,bike,0.04631945909663589 +,1500 - 2000,,,,,,2000 - 5000,car,0.05127689250314956 +,1500 - 2000,,,,,,2000 - 5000,pt,0.07917696836034209 +,1500 - 2000,,,,,,2000 - 5000,ride,0.014397871998404659 +,1500 - 2000,,,,,,2000 - 5000,walk,0.01709000238503994 +,1500 - 2000,,,,,,5000 - 10000,bike,0.022045388691979202 +,1500 - 2000,,,,,,5000 - 10000,car,0.05014154146035286 +,1500 - 2000,,,,,,5000 - 10000,pt,0.09913772578726267 +,1500 - 2000,,,,,,5000 - 10000,ride,0.01240198792987441 +,1500 - 2000,,,,,,5000 - 10000,walk,0.0011665572134838835 +,1500 - 2000,,,,,,10000 - 20000,bike,0.006661640309499392 +,1500 - 2000,,,,,,10000 - 20000,car,0.0341440688526599 +,1500 - 2000,,,,,,10000 - 20000,pt,0.06677062189126137 +,1500 - 2000,,,,,,10000 - 20000,ride,0.006336838794761074 +,1500 - 2000,,,,,,10000 - 20000,walk,0.0007864807634107508 +,1500 - 2000,,,,,,20000+,bike,0.0008266069780131096 +,1500 - 2000,,,,,,20000+,car,0.01711197866849559 +,1500 - 2000,,,,,,20000+,pt,0.02356392129134062 +,1500 - 2000,,,,,,20000+,ride,0.0030874339900264993 +,1500 - 2000,,,,,,20000+,walk,0.00016163077244841305 +,2000 - 2600,,,,,,0 - 1000,bike,0.033392386287582995 +,2000 - 2600,,,,,,0 - 1000,car,0.011750667173578672 +,2000 - 2600,,,,,,0 - 1000,pt,0.0033577219970918923 +,2000 - 2600,,,,,,0 - 1000,ride,0.004017675495628648 +,2000 - 2600,,,,,,0 - 1000,walk,0.2418248593465435 +,2000 - 2600,,,,,,1000 - 2000,bike,0.04164116355308919 +,2000 - 2600,,,,,,1000 - 2000,car,0.025052465274538565 +,2000 - 2600,,,,,,1000 - 2000,pt,0.022285701904862766 +,2000 - 2600,,,,,,1000 - 2000,ride,0.009089080033748823 +,2000 - 2600,,,,,,1000 - 2000,walk,0.049710929706035606 +,2000 - 2600,,,,,,2000 - 5000,bike,0.05481272765031414 +,2000 - 2600,,,,,,2000 - 5000,car,0.05465689217029469 +,2000 - 2600,,,,,,2000 - 5000,pt,0.07246185418226436 +,2000 - 2600,,,,,,2000 - 5000,ride,0.016283688182049984 +,2000 - 2600,,,,,,2000 - 5000,walk,0.012605924522216998 +,2000 - 2600,,,,,,5000 - 10000,bike,0.025026108853535477 +,2000 - 2600,,,,,,5000 - 10000,car,0.05914158168028776 +,2000 - 2600,,,,,,5000 - 10000,pt,0.08092420950471652 +,2000 - 2600,,,,,,5000 - 10000,ride,0.011312420356964784 +,2000 - 2600,,,,,,5000 - 10000,walk,0.0021656022200766834 +,2000 - 2600,,,,,,10000 - 20000,bike,0.006504474812229927 +,2000 - 2600,,,,,,10000 - 20000,car,0.040600067690888426 +,2000 - 2600,,,,,,10000 - 20000,pt,0.06149690620492426 +,2000 - 2600,,,,,,10000 - 20000,ride,0.006925265490792309 +,2000 - 2600,,,,,,10000 - 20000,walk,0.000373972377746059 +,2000 - 2600,,,,,,20000+,bike,0.0003281050789348971 +,2000 - 2600,,,,,,20000+,car,0.022844216579649936 +,2000 - 2600,,,,,,20000+,pt,0.024461236274549023 +,2000 - 2600,,,,,,20000+,ride,0.004757398109869234 +,2000 - 2600,,,,,,20000+,walk,0.00019469728499374168 +,2600 - 3000,,,,,,0 - 1000,bike,0.04149356389552307 +,2600 - 3000,,,,,,0 - 1000,car,0.011541052371186121 +,2600 - 3000,,,,,,0 - 1000,pt,0.0037531526647850847 +,2600 - 3000,,,,,,0 - 1000,ride,0.003712806863678218 +,2600 - 3000,,,,,,0 - 1000,walk,0.22998833449818365 +,2600 - 3000,,,,,,1000 - 2000,bike,0.04629551279380722 +,2600 - 3000,,,,,,1000 - 2000,car,0.02437230920610378 +,2600 - 3000,,,,,,1000 - 2000,pt,0.01820483747470896 +,2600 - 3000,,,,,,1000 - 2000,ride,0.010298110670851011 +,2600 - 3000,,,,,,1000 - 2000,walk,0.05234216881862283 +,2600 - 3000,,,,,,2000 - 5000,bike,0.05609731539035026 +,2600 - 3000,,,,,,2000 - 5000,car,0.05119912197216824 +,2600 - 3000,,,,,,2000 - 5000,pt,0.07075639388827012 +,2600 - 3000,,,,,,2000 - 5000,ride,0.017739166078363386 +,2600 - 3000,,,,,,2000 - 5000,walk,0.01368234409862979 +,2600 - 3000,,,,,,5000 - 10000,bike,0.025609365117338957 +,2600 - 3000,,,,,,5000 - 10000,car,0.05478211087071505 +,2600 - 3000,,,,,,5000 - 10000,pt,0.08041337693449645 +,2600 - 3000,,,,,,5000 - 10000,ride,0.014699866404994835 +,2600 - 3000,,,,,,5000 - 10000,walk,0.0014923007929669577 +,2600 - 3000,,,,,,10000 - 20000,bike,0.007413905642428567 +,2600 - 3000,,,,,,10000 - 20000,car,0.0441740931201671 +,2600 - 3000,,,,,,10000 - 20000,pt,0.06516252660688335 +,2600 - 3000,,,,,,10000 - 20000,ride,0.0076820260403931 +,2600 - 3000,,,,,,10000 - 20000,walk,0.0004195500443180223 +,2600 - 3000,,,,,,20000+,bike,0.0007696474863761435 +,2600 - 3000,,,,,,20000+,car,0.020873916468958396 +,2600 - 3000,,,,,,20000+,pt,0.021115394380993573 +,2600 - 3000,,,,,,20000+,ride,0.0038320950047968515 +,2600 - 3000,,,,,,20000+,walk,8.363439894094326e-05 +,3000 - 3600,,,,,,0 - 1000,bike,0.04157860120113375 +,3000 - 3600,,,,,,0 - 1000,car,0.009351445284182618 +,3000 - 3600,,,,,,0 - 1000,pt,0.0027408844677735806 +,3000 - 3600,,,,,,0 - 1000,ride,0.0042382321555965025 +,3000 - 3600,,,,,,0 - 1000,walk,0.21610179079244526 +,3000 - 3600,,,,,,1000 - 2000,bike,0.04803236184338368 +,3000 - 3600,,,,,,1000 - 2000,car,0.022248631980859034 +,3000 - 3600,,,,,,1000 - 2000,pt,0.015619417507692595 +,3000 - 3600,,,,,,1000 - 2000,ride,0.010511951434527544 +,3000 - 3600,,,,,,1000 - 2000,walk,0.05613023640342644 +,3000 - 3600,,,,,,2000 - 5000,bike,0.05581069982205347 +,3000 - 3600,,,,,,2000 - 5000,car,0.053405667270753264 +,3000 - 3600,,,,,,2000 - 5000,pt,0.06491269957617579 +,3000 - 3600,,,,,,2000 - 5000,ride,0.024697414925814096 +,3000 - 3600,,,,,,2000 - 5000,walk,0.013052461334264796 +,3000 - 3600,,,,,,5000 - 10000,bike,0.02683863292782313 +,3000 - 3600,,,,,,5000 - 10000,car,0.05980119365525028 +,3000 - 3600,,,,,,5000 - 10000,pt,0.07523156388803975 +,3000 - 3600,,,,,,5000 - 10000,ride,0.017382369500561722 +,3000 - 3600,,,,,,5000 - 10000,walk,0.0017691961741910986 +,3000 - 3600,,,,,,10000 - 20000,bike,0.007243872636375718 +,3000 - 3600,,,,,,10000 - 20000,car,0.044150523296818574 +,3000 - 3600,,,,,,10000 - 20000,pt,0.06584003707449934 +,3000 - 3600,,,,,,10000 - 20000,ride,0.008299496149072898 +,3000 - 3600,,,,,,10000 - 20000,walk,0.00030603852119757605 +,3000 - 3600,,,,,,20000+,bike,0.00031835636308559577 +,3000 - 3600,,,,,,20000+,car,0.025761044224548002 +,3000 - 3600,,,,,,20000+,pt,0.02433319974403415 +,3000 - 3600,,,,,,20000+,ride,0.003943685578895834 +,3000 - 3600,,,,,,20000+,walk,0.00034829426552370624 +,3600 - 4600,,,,,,0 - 1000,bike,0.04812497069835277 +,3600 - 4600,,,,,,0 - 1000,car,0.010255769635116858 +,3600 - 4600,,,,,,0 - 1000,pt,0.0021911932092977438 +,3600 - 4600,,,,,,0 - 1000,ride,0.004308896419719163 +,3600 - 4600,,,,,,0 - 1000,walk,0.222061063678996 +,3600 - 4600,,,,,,1000 - 2000,bike,0.05222795446432146 +,3600 - 4600,,,,,,1000 - 2000,car,0.024946590164324003 +,3600 - 4600,,,,,,1000 - 2000,pt,0.016397098000105494 +,3600 - 4600,,,,,,1000 - 2000,ride,0.013709334964290706 +,3600 - 4600,,,,,,1000 - 2000,walk,0.04659557655612417 +,3600 - 4600,,,,,,2000 - 5000,bike,0.06277345647528074 +,3600 - 4600,,,,,,2000 - 5000,car,0.05528807677612364 +,3600 - 4600,,,,,,2000 - 5000,pt,0.058211143004250746 +,3600 - 4600,,,,,,2000 - 5000,ride,0.023037066221417945 +,3600 - 4600,,,,,,2000 - 5000,walk,0.012241693578406765 +,3600 - 4600,,,,,,5000 - 10000,bike,0.02979373657899519 +,3600 - 4600,,,,,,5000 - 10000,car,0.04870684612791301 +,3600 - 4600,,,,,,5000 - 10000,pt,0.07131497927334177 +,3600 - 4600,,,,,,5000 - 10000,ride,0.016852787918811663 +,3600 - 4600,,,,,,5000 - 10000,walk,0.000894470130011945 +,3600 - 4600,,,,,,10000 - 20000,bike,0.008000182483496954 +,3600 - 4600,,,,,,10000 - 20000,car,0.04649423584852916 +,3600 - 4600,,,,,,10000 - 20000,pt,0.05903192135784184 +,3600 - 4600,,,,,,10000 - 20000,ride,0.00920368437432668 +,3600 - 4600,,,,,,10000 - 20000,walk,0.00014999177077495683 +,3600 - 4600,,,,,,20000+,bike,0.0006363087393218901 +,3600 - 4600,,,,,,20000+,car,0.02897031249884871 +,3600 - 4600,,,,,,20000+,pt,0.022942615139097094 +,3600 - 4600,,,,,,20000+,ride,0.004525654554221921 +,3600 - 4600,,,,,,20000+,walk,0.00011238935833910546 +,4600 - 5600,,,,,,0 - 1000,bike,0.048710281958938 +,4600 - 5600,,,,,,0 - 1000,car,0.01068288420014341 +,4600 - 5600,,,,,,0 - 1000,pt,0.0010734236028677698 +,4600 - 5600,,,,,,0 - 1000,ride,0.0044911703056080415 +,4600 - 5600,,,,,,0 - 1000,walk,0.20465119449744432 +,4600 - 5600,,,,,,1000 - 2000,bike,0.05304569917621215 +,4600 - 5600,,,,,,1000 - 2000,car,0.022262130681615536 +,4600 - 5600,,,,,,1000 - 2000,pt,0.014945223597211262 +,4600 - 5600,,,,,,1000 - 2000,ride,0.011531298350131435 +,4600 - 5600,,,,,,1000 - 2000,walk,0.04647191584785695 +,4600 - 5600,,,,,,2000 - 5000,bike,0.06347649814821983 +,4600 - 5600,,,,,,2000 - 5000,car,0.05620379250609639 +,4600 - 5600,,,,,,2000 - 5000,pt,0.058589829885607216 +,4600 - 5600,,,,,,2000 - 5000,ride,0.027753154917548345 +,4600 - 5600,,,,,,2000 - 5000,walk,0.009222595726733103 +,4600 - 5600,,,,,,5000 - 10000,bike,0.03247204038258978 +,4600 - 5600,,,,,,5000 - 10000,car,0.05593631594583977 +,4600 - 5600,,,,,,5000 - 10000,pt,0.0731190883691118 +,4600 - 5600,,,,,,5000 - 10000,ride,0.016857350757699483 +,4600 - 5600,,,,,,5000 - 10000,walk,0.0010029335475866892 +,4600 - 5600,,,,,,10000 - 20000,bike,0.006938126345210255 +,4600 - 5600,,,,,,10000 - 20000,car,0.050211715343122625 +,4600 - 5600,,,,,,10000 - 20000,pt,0.058945610288378684 +,4600 - 5600,,,,,,10000 - 20000,ride,0.010571442804107264 +,4600 - 5600,,,,,,10000 - 20000,walk,0.00019152349776608256 +,4600 - 5600,,,,,,20000+,bike,0.000653798655099752 +,4600 - 5600,,,,,,20000+,car,0.026895233621084425 +,4600 - 5600,,,,,,20000+,pt,0.028210661638814544 +,4600 - 5600,,,,,,20000+,ride,0.004537153391440641 +,4600 - 5600,,,,,,20000+,walk,0.00034591200991445915 +,5600+,,,,,,0 - 1000,bike,0.04081231520885232 +,5600+,,,,,,0 - 1000,car,0.011485812208722163 +,5600+,,,,,,0 - 1000,pt,0.002109615891521199 +,5600+,,,,,,0 - 1000,ride,0.005887824735804848 +,5600+,,,,,,0 - 1000,walk,0.205638840209066 +,5600+,,,,,,1000 - 2000,bike,0.05297341416560734 +,5600+,,,,,,1000 - 2000,car,0.025241394336974542 +,5600+,,,,,,1000 - 2000,pt,0.012876743472278455 +,5600+,,,,,,1000 - 2000,ride,0.014842482652864112 +,5600+,,,,,,1000 - 2000,walk,0.045522632703706566 +,5600+,,,,,,2000 - 5000,bike,0.061966221346868716 +,5600+,,,,,,2000 - 5000,car,0.05919732134622661 +,5600+,,,,,,2000 - 5000,pt,0.06030740476401822 +,5600+,,,,,,2000 - 5000,ride,0.02748154737239418 +,5600+,,,,,,2000 - 5000,walk,0.010299553904679378 +,5600+,,,,,,5000 - 10000,bike,0.02832312003019088 +,5600+,,,,,,5000 - 10000,car,0.05790663319230656 +,5600+,,,,,,5000 - 10000,pt,0.0670964118308329 +,5600+,,,,,,5000 - 10000,ride,0.016905860619605945 +,5600+,,,,,,5000 - 10000,walk,0.0017270482364382673 +,5600+,,,,,,10000 - 20000,bike,0.009517480524723213 +,5600+,,,,,,10000 - 20000,car,0.05232446569437673 +,5600+,,,,,,10000 - 20000,pt,0.05913464168614534 +,5600+,,,,,,10000 - 20000,ride,0.010731603541464917 +,5600+,,,,,,10000 - 20000,walk,0.0011074350420896743 +,5600+,,,,,,20000+,bike,0.0012576114443223207 +,5600+,,,,,,20000+,car,0.028100295532542622 +,5600+,,,,,,20000+,pt,0.02486302599822139 +,5600+,,,,,,20000+,ride,0.0042120823120815505 +,5600+,,,,,,20000+,walk,0.00014915999507293675 +,,very_low,,,,,0 - 1000,bike,0.03470681238249625 +,,very_low,,,,,0 - 1000,car,0.00664566014131732 +,,very_low,,,,,0 - 1000,pt,0.00760869670923257 +,,very_low,,,,,0 - 1000,ride,0.003124728592942877 +,,very_low,,,,,0 - 1000,walk,0.24469895082287613 +,,very_low,,,,,1000 - 2000,bike,0.04568124665405379 +,,very_low,,,,,1000 - 2000,car,0.01762264405555989 +,,very_low,,,,,1000 - 2000,pt,0.03176643067456601 +,,very_low,,,,,1000 - 2000,ride,0.009316330422067991 +,,very_low,,,,,1000 - 2000,walk,0.06872298007466462 +,,very_low,,,,,2000 - 5000,bike,0.050899816256695075 +,,very_low,,,,,2000 - 5000,car,0.033749602344892396 +,,very_low,,,,,2000 - 5000,pt,0.09486902097108738 +,,very_low,,,,,2000 - 5000,ride,0.015788943796908508 +,,very_low,,,,,2000 - 5000,walk,0.017570734627488975 +,,very_low,,,,,5000 - 10000,bike,0.02275352649147605 +,,very_low,,,,,5000 - 10000,car,0.027723850662754297 +,,very_low,,,,,5000 - 10000,pt,0.10839849171594336 +,,very_low,,,,,5000 - 10000,ride,0.012009036032519245 +,,very_low,,,,,5000 - 10000,walk,0.0015394033588941112 +,,very_low,,,,,10000 - 20000,bike,0.004407524031423659 +,,very_low,,,,,10000 - 20000,car,0.019387817923563564 +,,very_low,,,,,10000 - 20000,pt,0.0734242401915033 +,,very_low,,,,,10000 - 20000,ride,0.004587556923703756 +,,very_low,,,,,10000 - 20000,walk,0.001209125736696242 +,,very_low,,,,,20000+,bike,0.00041095332498231995 +,,very_low,,,,,20000+,car,0.009550733296281531 +,,very_low,,,,,20000+,pt,0.027400397002285904 +,,very_low,,,,,20000+,ride,0.004042413867625509 +,,very_low,,,,,20000+,walk,0.00038233091349725186 +,,low,,,,,0 - 1000,bike,0.041246470043080165 +,,low,,,,,0 - 1000,car,0.008910466432937744 +,,low,,,,,0 - 1000,pt,0.004293903536684401 +,,low,,,,,0 - 1000,ride,0.0033071684223682056 +,,low,,,,,0 - 1000,walk,0.24947973724879838 +,,low,,,,,1000 - 2000,bike,0.04961773460207695 +,,low,,,,,1000 - 2000,car,0.019329953900366764 +,,low,,,,,1000 - 2000,pt,0.025483245683043533 +,,low,,,,,1000 - 2000,ride,0.009185948988387805 +,,low,,,,,1000 - 2000,walk,0.05237722044190244 +,,low,,,,,2000 - 5000,bike,0.052466234584333846 +,,low,,,,,2000 - 5000,car,0.04278374195939727 +,,low,,,,,2000 - 5000,pt,0.08432283560598662 +,,low,,,,,2000 - 5000,ride,0.01702898827541261 +,,low,,,,,2000 - 5000,walk,0.015885053481139822 +,,low,,,,,5000 - 10000,bike,0.022672784003795167 +,,low,,,,,5000 - 10000,car,0.04475208451328922 +,,low,,,,,5000 - 10000,pt,0.09238030305339276 +,,low,,,,,5000 - 10000,ride,0.012354387654785704 +,,low,,,,,5000 - 10000,walk,0.0013482692004642123 +,,low,,,,,10000 - 20000,bike,0.00606581254281994 +,,low,,,,,10000 - 20000,car,0.031209843665855812 +,,low,,,,,10000 - 20000,pt,0.0653262065900355 +,,low,,,,,10000 - 20000,ride,0.006909609553827318 +,,low,,,,,10000 - 20000,walk,0.0005263054577439737 +,,low,,,,,20000+,bike,0.00047003197176413144 +,,low,,,,,20000+,car,0.013483593316552238 +,,low,,,,,20000+,pt,0.023496583359092254 +,,low,,,,,20000+,ride,0.0032589486332435533 +,,low,,,,,20000+,walk,2.6533277421522046e-05 +,,medium,,,,,0 - 1000,bike,0.03911700063927027 +,,medium,,,,,0 - 1000,car,0.011217731375356293 +,,medium,,,,,0 - 1000,pt,0.0036674145395910263 +,,medium,,,,,0 - 1000,ride,0.0040009596846765074 +,,medium,,,,,0 - 1000,walk,0.2331501383860879 +,,medium,,,,,1000 - 2000,bike,0.04558521294999408 +,,medium,,,,,1000 - 2000,car,0.02544280536333108 +,,medium,,,,,1000 - 2000,pt,0.019434896147692903 +,,medium,,,,,1000 - 2000,ride,0.009855997679676024 +,,medium,,,,,1000 - 2000,walk,0.0537914970446092 +,,medium,,,,,2000 - 5000,bike,0.05473092890667934 +,,medium,,,,,2000 - 5000,car,0.05384396289524313 +,,medium,,,,,2000 - 5000,pt,0.06617655580215061 +,,medium,,,,,2000 - 5000,ride,0.01916203987731777 +,,medium,,,,,2000 - 5000,walk,0.013040227239873697 +,,medium,,,,,5000 - 10000,bike,0.02574636249329226 +,,medium,,,,,5000 - 10000,car,0.05641048735516864 +,,medium,,,,,5000 - 10000,pt,0.08034283586815048 +,,medium,,,,,5000 - 10000,ride,0.01428950786852113 +,,medium,,,,,5000 - 10000,walk,0.0018418487953256962 +,,medium,,,,,10000 - 20000,bike,0.006346523947575605 +,,medium,,,,,10000 - 20000,car,0.04150377161860774 +,,medium,,,,,10000 - 20000,pt,0.0629525618466731 +,,medium,,,,,10000 - 20000,ride,0.0074802925048469545 +,,medium,,,,,10000 - 20000,walk,0.0003904121475787536 +,,medium,,,,,20000+,bike,0.000680433218446468 +,,medium,,,,,20000+,car,0.021857757194240046 +,,medium,,,,,20000+,pt,0.023834126105462355 +,,medium,,,,,20000+,ride,0.003906827817956772 +,,medium,,,,,20000+,walk,0.00019888268660406687 +,,high,,,,,0 - 1000,bike,0.041939214472666166 +,,high,,,,,0 - 1000,car,0.009296976205872368 +,,high,,,,,0 - 1000,pt,0.0019245320576424141 +,,high,,,,,0 - 1000,ride,0.0029544349795922243 +,,high,,,,,0 - 1000,walk,0.21013293332150743 +,,high,,,,,1000 - 2000,bike,0.046923469208975645 +,,high,,,,,1000 - 2000,car,0.022359163419126016 +,,high,,,,,1000 - 2000,pt,0.01598105459224138 +,,high,,,,,1000 - 2000,ride,0.00969781710878335 +,,high,,,,,1000 - 2000,walk,0.04629924755619724 +,,high,,,,,2000 - 5000,bike,0.06385560203538197 +,,high,,,,,2000 - 5000,car,0.05664195116716653 +,,high,,,,,2000 - 5000,pt,0.06341010532730178 +,,high,,,,,2000 - 5000,ride,0.02062977816711411 +,,high,,,,,2000 - 5000,walk,0.013022335460471058 +,,high,,,,,5000 - 10000,bike,0.03340249625957814 +,,high,,,,,5000 - 10000,car,0.05522082435189495 +,,high,,,,,5000 - 10000,pt,0.0768163134346059 +,,high,,,,,5000 - 10000,ride,0.014151567281204704 +,,high,,,,,5000 - 10000,walk,0.0010048399305621642 +,,high,,,,,10000 - 20000,bike,0.009283542374603758 +,,high,,,,,10000 - 20000,car,0.05082160352876232 +,,high,,,,,10000 - 20000,pt,0.06361373744580245 +,,high,,,,,10000 - 20000,ride,0.009672528719534406 +,,high,,,,,10000 - 20000,walk,0.000206579718736878 +,,high,,,,,20000+,bike,0.00043427613503130015 +,,high,,,,,20000+,car,0.0302730459828126 +,,high,,,,,20000+,pt,0.025319864147407976 +,,high,,,,,20000+,ride,0.004383208486815366 +,,high,,,,,20000+,walk,0.00032695712260737283 +,,very_high,,,,,0 - 1000,bike,0.04115639089698632 +,,very_high,,,,,0 - 1000,car,0.012722254325776056 +,,very_high,,,,,0 - 1000,pt,0.0021241404664278096 +,,very_high,,,,,0 - 1000,ride,0.006479698270689627 +,,very_high,,,,,0 - 1000,walk,0.21073962672422653 +,,very_high,,,,,1000 - 2000,bike,0.04962428972352181 +,,very_high,,,,,1000 - 2000,car,0.02750613621612044 +,,very_high,,,,,1000 - 2000,pt,0.013034837734497939 +,,very_high,,,,,1000 - 2000,ride,0.015305658534843499 +,,very_high,,,,,1000 - 2000,walk,0.044574423999390184 +,,very_high,,,,,2000 - 5000,bike,0.054368817956349784 +,,very_high,,,,,2000 - 5000,car,0.06181110351680053 +,,very_high,,,,,2000 - 5000,pt,0.05826964813749158 +,,very_high,,,,,2000 - 5000,ride,0.02786841949326947 +,,very_high,,,,,2000 - 5000,walk,0.008853536562096663 +,,very_high,,,,,5000 - 10000,bike,0.02620750926316137 +,,very_high,,,,,5000 - 10000,car,0.06319670192405069 +,,very_high,,,,,5000 - 10000,pt,0.06408662985856173 +,,very_high,,,,,5000 - 10000,ride,0.016694136150129876 +,,very_high,,,,,5000 - 10000,walk,0.0017755406053744846 +,,very_high,,,,,10000 - 20000,bike,0.00916609078166196 +,,very_high,,,,,10000 - 20000,car,0.05422199392062498 +,,very_high,,,,,10000 - 20000,pt,0.05651365669095907 +,,very_high,,,,,10000 - 20000,ride,0.010116960000757621 +,,very_high,,,,,10000 - 20000,walk,0.0011846243905422437 +,,very_high,,,,,20000+,bike,0.001489759414370527 +,,very_high,,,,,20000+,car,0.03041067782980122 +,,very_high,,,,,20000+,pt,0.026469066188086976 +,,very_high,,,,,20000+,ride,0.003984235726180184 +,,very_high,,,,,20000+,walk,4.343469724887364e-05 +,,,child,,,,0 - 1000,bike,0.08797447704562544 +,,,child,,,,0 - 1000,car,6.201632379149975e-05 +,,,child,,,,0 - 1000,pt,0.0024803584947749153 +,,,child,,,,0 - 1000,ride,0.022491490648019048 +,,,child,,,,0 - 1000,walk,0.34497127297047747 +,,,child,,,,1000 - 2000,bike,0.07427123298204033 +,,,child,,,,1000 - 2000,car,8.634641491562534e-05 +,,,child,,,,1000 - 2000,pt,0.024748734059472668 +,,,child,,,,1000 - 2000,ride,0.05920313740289035 +,,,child,,,,1000 - 2000,walk,0.08264286445303023 +,,,child,,,,2000 - 5000,bike,0.040113313236642925 +,,,child,,,,2000 - 5000,car,0.0 +,,,child,,,,2000 - 5000,pt,0.04788791080274093 +,,,child,,,,2000 - 5000,ride,0.08746818283628316 +,,,child,,,,2000 - 5000,walk,0.01079786692437333 +,,,child,,,,5000 - 10000,bike,0.004805099180886954 +,,,child,,,,5000 - 10000,car,0.0 +,,,child,,,,5000 - 10000,pt,0.02483365972541551 +,,,child,,,,5000 - 10000,ride,0.04367184908604869 +,,,child,,,,5000 - 10000,walk,0.0012955063068117055 +,,,child,,,,10000 - 20000,bike,0.0 +,,,child,,,,10000 - 20000,car,0.0 +,,,child,,,,10000 - 20000,pt,0.006359525422654798 +,,,child,,,,10000 - 20000,ride,0.022509064689085725 +,,,child,,,,10000 - 20000,walk,0.0005490547723028881 +,,,child,,,,20000+,bike,0.0 +,,,child,,,,20000+,car,0.0 +,,,child,,,,20000+,pt,0.003692397229544375 +,,,child,,,,20000+,ride,0.006852048186314848 +,,,child,,,,20000+,walk,0.00023259080585655162 +,,,homemaker,,,,0 - 1000,bike,0.033151806966215495 +,,,homemaker,,,,0 - 1000,car,0.017096887825047154 +,,,homemaker,,,,0 - 1000,pt,0.00741600650756041 +,,,homemaker,,,,0 - 1000,ride,0.003227524589354174 +,,,homemaker,,,,0 - 1000,walk,0.2885201729519205 +,,,homemaker,,,,1000 - 2000,bike,0.059202386584587534 +,,,homemaker,,,,1000 - 2000,car,0.04025033806294192 +,,,homemaker,,,,1000 - 2000,pt,0.0273866228320414 +,,,homemaker,,,,1000 - 2000,ride,0.006330981798157926 +,,,homemaker,,,,1000 - 2000,walk,0.07860929727267789 +,,,homemaker,,,,2000 - 5000,bike,0.04046916174769916 +,,,homemaker,,,,2000 - 5000,car,0.09370896621243806 +,,,homemaker,,,,2000 - 5000,pt,0.06043872284166353 +,,,homemaker,,,,2000 - 5000,ride,0.01865689653854778 +,,,homemaker,,,,2000 - 5000,walk,0.022329877577321945 +,,,homemaker,,,,5000 - 10000,bike,0.01007054931926354 +,,,homemaker,,,,5000 - 10000,car,0.0517419983300954 +,,,homemaker,,,,5000 - 10000,pt,0.03961389187595275 +,,,homemaker,,,,5000 - 10000,ride,0.010868096212678242 +,,,homemaker,,,,5000 - 10000,walk,0.001119555974530009 +,,,homemaker,,,,10000 - 20000,bike,0.0013630691808948023 +,,,homemaker,,,,10000 - 20000,car,0.03297441162678622 +,,,homemaker,,,,10000 - 20000,pt,0.02062081117537517 +,,,homemaker,,,,10000 - 20000,ride,0.011126178544959902 +,,,homemaker,,,,10000 - 20000,walk,0.00036647317815220574 +,,,homemaker,,,,20000+,bike,0.0 +,,,homemaker,,,,20000+,car,0.009155831126737714 +,,,homemaker,,,,20000+,pt,0.012519960684027233 +,,,homemaker,,,,20000+,ride,0.0008732491830433166 +,,,homemaker,,,,20000+,walk,0.0007902732793286338 +,,,retiree,,,,0 - 1000,bike,0.02803433594931146 +,,,retiree,,,,0 - 1000,car,0.015941591246107053 +,,,retiree,,,,0 - 1000,pt,0.008575659044152376 +,,,retiree,,,,0 - 1000,ride,0.004703006319317826 +,,,retiree,,,,0 - 1000,walk,0.2846270602599153 +,,,retiree,,,,1000 - 2000,bike,0.028632402921317548 +,,,retiree,,,,1000 - 2000,car,0.03832910463414241 +,,,retiree,,,,1000 - 2000,pt,0.032952941858310617 +,,,retiree,,,,1000 - 2000,ride,0.01168231876013348 +,,,retiree,,,,1000 - 2000,walk,0.06888892277570885 +,,,retiree,,,,2000 - 5000,bike,0.024130989117319465 +,,,retiree,,,,2000 - 5000,car,0.08303426111525544 +,,,retiree,,,,2000 - 5000,pt,0.06608361005813324 +,,,retiree,,,,2000 - 5000,ride,0.022716231901212754 +,,,retiree,,,,2000 - 5000,walk,0.019871262845775835 +,,,retiree,,,,5000 - 10000,bike,0.006523730886384607 +,,,retiree,,,,5000 - 10000,car,0.06094744140940063 +,,,retiree,,,,5000 - 10000,pt,0.053010003113323496 +,,,retiree,,,,5000 - 10000,ride,0.017547687886227004 +,,,retiree,,,,5000 - 10000,walk,0.0021268832316059514 +,,,retiree,,,,10000 - 20000,bike,0.0027508467688472844 +,,,retiree,,,,10000 - 20000,car,0.033492350519474676 +,,,retiree,,,,10000 - 20000,pt,0.03546978470611497 +,,,retiree,,,,10000 - 20000,ride,0.011801193617084637 +,,,retiree,,,,10000 - 20000,walk,0.0008557046894452218 +,,,retiree,,,,20000+,bike,0.00023940434380408774 +,,,retiree,,,,20000+,car,0.017904493427535573 +,,,retiree,,,,20000+,pt,0.012123209640667017 +,,,retiree,,,,20000+,ride,0.006940672029918143 +,,,retiree,,,,20000+,walk,6.289492405314406e-05 +,,,unemployed,,,,0 - 1000,bike,0.028473036460034625 +,,,unemployed,,,,0 - 1000,car,0.01206763565093488 +,,,unemployed,,,,0 - 1000,pt,0.0066246780260138475 +,,,unemployed,,,,0 - 1000,ride,0.0002788219068772566 +,,,unemployed,,,,0 - 1000,walk,0.2900101906591626 +,,,unemployed,,,,1000 - 2000,bike,0.06262667569850242 +,,,unemployed,,,,1000 - 2000,car,0.028896506543494183 +,,,unemployed,,,,1000 - 2000,pt,0.02660096577041169 +,,,unemployed,,,,1000 - 2000,ride,0.004606811635231683 +,,,unemployed,,,,1000 - 2000,walk,0.06174361569499453 +,,,unemployed,,,,2000 - 5000,bike,0.049515654255582325 +,,,unemployed,,,,2000 - 5000,car,0.039677051190551225 +,,,unemployed,,,,2000 - 5000,pt,0.074237083423941 +,,,unemployed,,,,2000 - 5000,ride,0.006255362566946953 +,,,unemployed,,,,2000 - 5000,walk,0.029885632074737396 +,,,unemployed,,,,5000 - 10000,bike,0.0253519458398073 +,,,unemployed,,,,5000 - 10000,car,0.03422879715974879 +,,,unemployed,,,,5000 - 10000,pt,0.08366977505423692 +,,,unemployed,,,,5000 - 10000,ride,0.005622178708513779 +,,,unemployed,,,,5000 - 10000,walk,0.0 +,,,unemployed,,,,10000 - 20000,bike,0.00417328864141108 +,,,unemployed,,,,10000 - 20000,car,0.013674574163855544 +,,,unemployed,,,,10000 - 20000,pt,0.0734624973685337 +,,,unemployed,,,,10000 - 20000,ride,0.0060156277308030595 +,,,unemployed,,,,10000 - 20000,walk,0.003155252756840654 +,,,unemployed,,,,20000+,bike,0.0 +,,,unemployed,,,,20000+,car,0.007882285969591084 +,,,unemployed,,,,20000+,pt,0.020180847789405588 +,,,unemployed,,,,20000+,ride,0.0010832072598357227 +,,,unemployed,,,,20000+,walk,0.0 +,,,school,,,,0 - 1000,bike,0.0619376780579444 +,,,school,,,,0 - 1000,car,0.00018646363364950395 +,,,school,,,,0 - 1000,pt,0.0036144502125357576 +,,,school,,,,0 - 1000,ride,0.009476152922497092 +,,,school,,,,0 - 1000,walk,0.23637839402380614 +,,,school,,,,1000 - 2000,bike,0.09030920523969665 +,,,school,,,,1000 - 2000,car,0.0006203364901544102 +,,,school,,,,1000 - 2000,pt,0.02679399804666396 +,,,school,,,,1000 - 2000,ride,0.02798996132264418 +,,,school,,,,1000 - 2000,walk,0.051908607065914554 +,,,school,,,,2000 - 5000,bike,0.0802477950696995 +,,,school,,,,2000 - 5000,car,0.0022886729071440455 +,,,school,,,,2000 - 5000,pt,0.13269861709274064 +,,,school,,,,2000 - 5000,ride,0.056685545379448206 +,,,school,,,,2000 - 5000,walk,0.006499265127616391 +,,,school,,,,5000 - 10000,bike,0.009360364028456122 +,,,school,,,,5000 - 10000,car,0.0016790952562727782 +,,,school,,,,5000 - 10000,pt,0.10277327099819801 +,,,school,,,,5000 - 10000,ride,0.035425118596691396 +,,,school,,,,5000 - 10000,walk,0.00038678535209195385 +,,,school,,,,10000 - 20000,bike,0.0005980589328777765 +,,,school,,,,10000 - 20000,car,0.00132075569093999 +,,,school,,,,10000 - 20000,pt,0.03797995833954222 +,,,school,,,,10000 - 20000,ride,0.010978356237460405 +,,,school,,,,10000 - 20000,walk,0.00017780241335867242 +,,,school,,,,20000+,bike,0.00022130407576553514 +,,,school,,,,20000+,car,0.0003848559121911246 +,,,school,,,,20000+,pt,0.006654790004719217 +,,,school,,,,20000+,ride,0.004025326946849407 +,,,school,,,,20000+,walk,0.0003990146224299257 +,,,student,,,,0 - 1000,bike,0.02356220023873468 +,,,student,,,,0 - 1000,car,0.004368932828123815 +,,,student,,,,0 - 1000,pt,0.0037978777376658055 +,,,student,,,,0 - 1000,ride,0.001523895793406923 +,,,student,,,,0 - 1000,walk,0.17076517686042786 +,,,student,,,,1000 - 2000,bike,0.03738080250314786 +,,,student,,,,1000 - 2000,car,0.007682269194679312 +,,,student,,,,1000 - 2000,pt,0.02217394556697622 +,,,student,,,,1000 - 2000,ride,0.0026565452052639033 +,,,student,,,,1000 - 2000,walk,0.0545770671484238 +,,,student,,,,2000 - 5000,bike,0.06432907652809451 +,,,student,,,,2000 - 5000,car,0.019100392334253705 +,,,student,,,,2000 - 5000,pt,0.11217963558936651 +,,,student,,,,2000 - 5000,ride,0.009152359825369636 +,,,student,,,,2000 - 5000,walk,0.01642894417545653 +,,,student,,,,5000 - 10000,bike,0.03403760366604839 +,,,student,,,,5000 - 10000,car,0.022849192327934077 +,,,student,,,,5000 - 10000,pt,0.1641983623123737 +,,,student,,,,5000 - 10000,ride,0.006876201166208598 +,,,student,,,,5000 - 10000,walk,0.001068722058597145 +,,,student,,,,10000 - 20000,bike,0.006446614720123388 +,,,student,,,,10000 - 20000,car,0.01861014858339467 +,,,student,,,,10000 - 20000,pt,0.1186036950293715 +,,,student,,,,10000 - 20000,ride,0.005896199856583919 +,,,student,,,,10000 - 20000,walk,0.00035154920720382153 +,,,student,,,,20000+,bike,0.0006766977939909812 +,,,student,,,,20000+,car,0.013921636477701456 +,,,student,,,,20000+,pt,0.051929004231407894 +,,,student,,,,20000+,ride,0.004855251039669398 +,,,student,,,,20000+,walk,0.0 +,,,trainee,,,,0 - 1000,bike,0.012521503764836246 +,,,trainee,,,,0 - 1000,car,0.007298356193634069 +,,,trainee,,,,0 - 1000,pt,0.0016668796511095385 +,,,trainee,,,,0 - 1000,ride,0.0030825771686377942 +,,,trainee,,,,0 - 1000,walk,0.16500369285009003 +,,,trainee,,,,1000 - 2000,bike,0.01779794495007991 +,,,trainee,,,,1000 - 2000,car,0.003149993006655295 +,,,trainee,,,,1000 - 2000,pt,0.02268559042076619 +,,,trainee,,,,1000 - 2000,ride,0.0040664047997533695 +,,,trainee,,,,1000 - 2000,walk,0.03960900446868207 +,,,trainee,,,,2000 - 5000,bike,0.026071327681647948 +,,,trainee,,,,2000 - 5000,car,0.03644433692867858 +,,,trainee,,,,2000 - 5000,pt,0.08960321544062727 +,,,trainee,,,,2000 - 5000,ride,0.009613160445992438 +,,,trainee,,,,2000 - 5000,walk,0.004017733129450831 +,,,trainee,,,,5000 - 10000,bike,0.013474805871587335 +,,,trainee,,,,5000 - 10000,car,0.040396871970504115 +,,,trainee,,,,5000 - 10000,pt,0.15447499374463067 +,,,trainee,,,,5000 - 10000,ride,0.015751740222842803 +,,,trainee,,,,5000 - 10000,walk,0.0027646110583024472 +,,,trainee,,,,10000 - 20000,bike,0.003591734235218891 +,,,trainee,,,,10000 - 20000,car,0.030580148709696862 +,,,trainee,,,,10000 - 20000,pt,0.18379854961876674 +,,,trainee,,,,10000 - 20000,ride,0.011237305431667793 +,,,trainee,,,,10000 - 20000,walk,0.0 +,,,trainee,,,,20000+,bike,0.0006740957100523827 +,,,trainee,,,,20000+,car,0.02240635651365801 +,,,trainee,,,,20000+,pt,0.07244745989852527 +,,,trainee,,,,20000+,ride,0.0047900616550283995 +,,,trainee,,,,20000+,walk,0.0009795444588767178 +,,,job_full_time,,,,0 - 1000,bike,0.03013933778567867 +,,,job_full_time,,,,0 - 1000,car,0.010491876131548051 +,,,job_full_time,,,,0 - 1000,pt,0.001359880974463405 +,,,job_full_time,,,,0 - 1000,ride,0.0009737141067612931 +,,,job_full_time,,,,0 - 1000,walk,0.18555797811248625 +,,,job_full_time,,,,1000 - 2000,bike,0.037575281838997145 +,,,job_full_time,,,,1000 - 2000,car,0.024342572407602656 +,,,job_full_time,,,,1000 - 2000,pt,0.012125099939636425 +,,,job_full_time,,,,1000 - 2000,ride,0.002542177597938074 +,,,job_full_time,,,,1000 - 2000,walk,0.037130732560095164 +,,,job_full_time,,,,2000 - 5000,bike,0.06339556433079593 +,,,job_full_time,,,,2000 - 5000,car,0.0552753773757432 +,,,job_full_time,,,,2000 - 5000,pt,0.05667774866351121 +,,,job_full_time,,,,2000 - 5000,ride,0.006877058685280377 +,,,job_full_time,,,,2000 - 5000,walk,0.01074447339096398 +,,,job_full_time,,,,5000 - 10000,bike,0.0419792037561615 +,,,job_full_time,,,,5000 - 10000,car,0.07519810125586637 +,,,job_full_time,,,,5000 - 10000,pt,0.08791473887561047 +,,,job_full_time,,,,5000 - 10000,ride,0.00685184204142721 +,,,job_full_time,,,,5000 - 10000,walk,0.0016415405564862843 +,,,job_full_time,,,,10000 - 20000,bike,0.013184442284954296 +,,,job_full_time,,,,10000 - 20000,car,0.07129160225405813 +,,,job_full_time,,,,10000 - 20000,pt,0.08151361323102535 +,,,job_full_time,,,,10000 - 20000,ride,0.004736631628841786 +,,,job_full_time,,,,10000 - 20000,walk,0.0005586884147394083 +,,,job_full_time,,,,20000+,bike,0.0012490068587271434 +,,,job_full_time,,,,20000+,car,0.040567119638494215 +,,,job_full_time,,,,20000+,pt,0.03520659977092769 +,,,job_full_time,,,,20000+,ride,0.0026266559172975616 +,,,job_full_time,,,,20000+,walk,0.00027133961388065865 +,,,job_part_time,,,,0 - 1000,bike,0.05788930935252188 +,,,job_part_time,,,,0 - 1000,car,0.012600124011170702 +,,,job_part_time,,,,0 - 1000,pt,0.0023022363889259505 +,,,job_part_time,,,,0 - 1000,ride,0.0008752863426891199 +,,,job_part_time,,,,0 - 1000,walk,0.20651593711522592 +,,,job_part_time,,,,1000 - 2000,bike,0.06120183442984339 +,,,job_part_time,,,,1000 - 2000,car,0.026187254055777424 +,,,job_part_time,,,,1000 - 2000,pt,0.013670138011963498 +,,,job_part_time,,,,1000 - 2000,ride,0.0038638859763716928 +,,,job_part_time,,,,1000 - 2000,walk,0.03876773747170083 +,,,job_part_time,,,,2000 - 5000,bike,0.0764631165846227 +,,,job_part_time,,,,2000 - 5000,car,0.06256351536224046 +,,,job_part_time,,,,2000 - 5000,pt,0.0600216466376844 +,,,job_part_time,,,,2000 - 5000,ride,0.008006383164056257 +,,,job_part_time,,,,2000 - 5000,walk,0.011413641974962621 +,,,job_part_time,,,,5000 - 10000,bike,0.03941403115041603 +,,,job_part_time,,,,5000 - 10000,car,0.05348787502585056 +,,,job_part_time,,,,5000 - 10000,pt,0.08638440908282295 +,,,job_part_time,,,,5000 - 10000,ride,0.0055825350733728395 +,,,job_part_time,,,,5000 - 10000,walk,0.001733409888793159 +,,,job_part_time,,,,10000 - 20000,bike,0.008134087823778351 +,,,job_part_time,,,,10000 - 20000,car,0.03788210760082822 +,,,job_part_time,,,,10000 - 20000,pt,0.07732057340507803 +,,,job_part_time,,,,10000 - 20000,ride,0.0042621188147322865 +,,,job_part_time,,,,10000 - 20000,walk,0.0001262979901470656 +,,,job_part_time,,,,20000+,bike,0.0005251906869208823 +,,,job_part_time,,,,20000+,car,0.014722131884886766 +,,,job_part_time,,,,20000+,pt,0.025900077307611968 +,,,job_part_time,,,,20000+,ride,0.002065947509913174 +,,,job_part_time,,,,20000+,walk,0.00011715987509080905 +,,,other,,,,0 - 1000,bike,0.04930384107342923 +,,,other,,,,0 - 1000,car,0.011788223194744126 +,,,other,,,,0 - 1000,pt,0.0013729829565074935 +,,,other,,,,0 - 1000,ride,0.0017187249197642756 +,,,other,,,,0 - 1000,walk,0.2937438405365747 +,,,other,,,,1000 - 2000,bike,0.04544589572290246 +,,,other,,,,1000 - 2000,car,0.03192982545506678 +,,,other,,,,1000 - 2000,pt,0.01702973565121598 +,,,other,,,,1000 - 2000,ride,0.00312445438232703 +,,,other,,,,1000 - 2000,walk,0.09423363848630212 +,,,other,,,,2000 - 5000,bike,0.053031784306758735 +,,,other,,,,2000 - 5000,car,0.0587406381989171 +,,,other,,,,2000 - 5000,pt,0.06116382459167897 +,,,other,,,,2000 - 5000,ride,0.00818344587606724 +,,,other,,,,2000 - 5000,walk,0.021102897779573446 +,,,other,,,,5000 - 10000,bike,0.02352965810583334 +,,,other,,,,5000 - 10000,car,0.04601077851041131 +,,,other,,,,5000 - 10000,pt,0.06310661719139919 +,,,other,,,,5000 - 10000,ride,0.012425388935623692 +,,,other,,,,5000 - 10000,walk,0.0008639159974911152 +,,,other,,,,10000 - 20000,bike,0.0026416428973974806 +,,,other,,,,10000 - 20000,car,0.030993548409293373 +,,,other,,,,10000 - 20000,pt,0.03167623382196632 +,,,other,,,,10000 - 20000,ride,0.005060710378546935 +,,,other,,,,10000 - 20000,walk,0.0 +,,,other,,,,20000+,bike,0.0 +,,,other,,,,20000+,car,0.014056919018589648 +,,,other,,,,20000+,pt,0.01373268963452078 +,,,other,,,,20000+,ride,0.003868050560621929 +,,,other,,,,20000+,walk,0.00012009340647531543 +,,,,yes,,,0 - 1000,bike,0.03299714650207197 +,,,,yes,,,0 - 1000,car,0.017027002619882433 +,,,,yes,,,0 - 1000,pt,0.001447150167269608 +,,,,yes,,,0 - 1000,ride,0.006170949721414438 +,,,,yes,,,0 - 1000,walk,0.19741858334434997 +,,,,yes,,,1000 - 2000,bike,0.037957883407812 +,,,,yes,,,1000 - 2000,car,0.0394529320636818 +,,,,yes,,,1000 - 2000,pt,0.011451027027536226 +,,,,yes,,,1000 - 2000,ride,0.01641946900059602 +,,,,yes,,,1000 - 2000,walk,0.04651342478512875 +,,,,yes,,,2000 - 5000,bike,0.040949944088661795 +,,,,yes,,,2000 - 5000,car,0.08811056075566344 +,,,,yes,,,2000 - 5000,pt,0.039127179750233054 +,,,,yes,,,2000 - 5000,ride,0.03091813771250092 +,,,,yes,,,2000 - 5000,walk,0.011315043201306118 +,,,,yes,,,5000 - 10000,bike,0.019017621788004747 +,,,,yes,,,5000 - 10000,car,0.08791860269484927 +,,,,yes,,,5000 - 10000,pt,0.049812041547093816 +,,,,yes,,,5000 - 10000,ride,0.02156168933687161 +,,,,yes,,,5000 - 10000,walk,0.0013154378478287684 +,,,,yes,,,10000 - 20000,bike,0.007202583446410026 +,,,,yes,,,10000 - 20000,car,0.07045195617229356 +,,,,yes,,,10000 - 20000,pt,0.049749815066314804 +,,,,yes,,,10000 - 20000,ride,0.01138530413745259 +,,,,yes,,,10000 - 20000,walk,0.0004945201895780406 +,,,,yes,,,20000+,bike,0.0006992742746565023 +,,,,yes,,,20000+,car,0.03798906450435869 +,,,,yes,,,20000+,pt,0.0193490440421187 +,,,,yes,,,20000+,ride,0.005583932136757436 +,,,,yes,,,20000+,walk,0.0001926786673028211 +,,,,no,,,0 - 1000,bike,0.05634302463139612 +,,,,no,,,0 - 1000,car,0.00013584823212312846 +,,,,no,,,0 - 1000,pt,0.004223086048582885 +,,,,no,,,0 - 1000,ride,0.0004017508303949193 +,,,,no,,,0 - 1000,walk,0.2580471733726988 +,,,,no,,,1000 - 2000,bike,0.07836944366732389 +,,,,no,,,1000 - 2000,car,0.0010390801814425925 +,,,,no,,,1000 - 2000,pt,0.024784889955329636 +,,,,no,,,1000 - 2000,ride,0.001974025369215151 +,,,,no,,,1000 - 2000,walk,0.06126594139823154 +,,,,no,,,2000 - 5000,bike,0.0830645845503165 +,,,,no,,,2000 - 5000,car,0.0021086321349634155 +,,,,no,,,2000 - 5000,pt,0.11041897753164191 +,,,,no,,,2000 - 5000,ride,0.004147919011818219 +,,,,no,,,2000 - 5000,walk,0.014386694755001183 +,,,,no,,,5000 - 10000,bike,0.023823332264979735 +,,,,no,,,5000 - 10000,car,0.00388669513192251 +,,,,no,,,5000 - 10000,pt,0.1264514357248081 +,,,,no,,,5000 - 10000,ride,0.003639247377874085 +,,,,no,,,5000 - 10000,walk,0.001313036391096564 +,,,,no,,,10000 - 20000,bike,0.006499542597368016 +,,,,no,,,10000 - 20000,car,0.0023781765029886976 +,,,,no,,,10000 - 20000,pt,0.0898320669228972 +,,,,no,,,10000 - 20000,ride,0.002272185599137619 +,,,,no,,,10000 - 20000,walk,0.0002215003222691513 +,,,,no,,,20000+,bike,0.000145317955987046 +,,,,no,,,20000+,car,0.0011123694286944764 +,,,,no,,,20000+,pt,0.03502771576797686 +,,,,no,,,20000+,ride,0.0023288783488159014 +,,,,no,,,20000+,walk,0.00035742799270410425 +,,,,unknown,,,0 - 1000,bike,0.04769480779566642 +,,,,unknown,,,0 - 1000,car,0.0008590409073678141 +,,,,unknown,,,0 - 1000,pt,0.006885971870744252 +,,,,unknown,,,0 - 1000,ride,0.0006475563097244331 +,,,,unknown,,,0 - 1000,walk,0.27397601349162803 +,,,,unknown,,,1000 - 2000,bike,0.054805139453978344 +,,,,unknown,,,1000 - 2000,car,0.0012430677265334652 +,,,,unknown,,,1000 - 2000,pt,0.033242677055117804 +,,,,unknown,,,1000 - 2000,ride,0.001843007903016114 +,,,,unknown,,,1000 - 2000,walk,0.05955224874154208 +,,,,unknown,,,2000 - 5000,bike,0.0754255586265544 +,,,,unknown,,,2000 - 5000,car,0.0026690299351213756 +,,,,unknown,,,2000 - 5000,pt,0.11282652655099872 +,,,,unknown,,,2000 - 5000,ride,0.004859468679865614 +,,,,unknown,,,2000 - 5000,walk,0.016905290890743576 +,,,,unknown,,,5000 - 10000,bike,0.040669840042011456 +,,,,unknown,,,5000 - 10000,car,0.0040567764617673455 +,,,,unknown,,,5000 - 10000,pt,0.12634356429185695 +,,,,unknown,,,5000 - 10000,ride,0.0037836090315891847 +,,,,unknown,,,5000 - 10000,walk,0.0018965482493745298 +,,,,unknown,,,10000 - 20000,bike,0.007171532455515839 +,,,,unknown,,,10000 - 20000,car,0.002130589192925642 +,,,,unknown,,,10000 - 20000,pt,0.08127495675667207 +,,,,unknown,,,10000 - 20000,ride,0.003495551578556325 +,,,,unknown,,,10000 - 20000,walk,0.0006500800744487337 +,,,,unknown,,,20000+,bike,0.0006590572012220345 +,,,,unknown,,,20000+,car,0.0012317691156839004 +,,,,unknown,,,20000+,pt,0.03140217989755744 +,,,,unknown,,,20000+,ride,0.0016057119926328554 +,,,,unknown,,,20000+,walk,0.00019282771958326642 +,,,,,yes,,0 - 1000,bike,0.04698912487219602 +,,,,,yes,,0 - 1000,car,0.010350504080342545 +,,,,,yes,,0 - 1000,pt,0.002005078100267684 +,,,,,yes,,0 - 1000,ride,0.0025947594982208538 +,,,,,yes,,0 - 1000,walk,0.20633753959789913 +,,,,,yes,,1000 - 2000,bike,0.05672700261397835 +,,,,,yes,,1000 - 2000,car,0.02334919632026469 +,,,,,yes,,1000 - 2000,pt,0.014170631577363242 +,,,,,yes,,1000 - 2000,ride,0.007566742947430185 +,,,,,yes,,1000 - 2000,walk,0.04705114468466599 +,,,,,yes,,2000 - 5000,bike,0.07181957575497981 +,,,,,yes,,2000 - 5000,car,0.05407055126753428 +,,,,,yes,,2000 - 5000,pt,0.06176666900576435 +,,,,,yes,,2000 - 5000,ride,0.016015472342173622 +,,,,,yes,,2000 - 5000,walk,0.013218894993642248 +,,,,,yes,,5000 - 10000,bike,0.03532074465966296 +,,,,,yes,,5000 - 10000,car,0.055207432470111104 +,,,,,yes,,5000 - 10000,pt,0.08166242688776854 +,,,,,yes,,5000 - 10000,ride,0.01246459413877433 +,,,,,yes,,5000 - 10000,walk,0.001461452030304094 +,,,,,yes,,10000 - 20000,bike,0.00947171900707687 +,,,,,yes,,10000 - 20000,car,0.043678018613143035 +,,,,,yes,,10000 - 20000,pt,0.0666479672383282 +,,,,,yes,,10000 - 20000,ride,0.006622958957568501 +,,,,,yes,,10000 - 20000,walk,0.0003518277954969559 +,,,,,yes,,20000+,bike,0.0008554476676628036 +,,,,,yes,,20000+,car,0.022583799318947207 +,,,,,yes,,20000+,pt,0.02579374805433979 +,,,,,yes,,20000+,ride,0.003647041013066067 +,,,,,yes,,20000+,walk,0.0001979344910266161 +,,,,,no,,0 - 1000,bike,0.0039012262016867227 +,,,,,no,,0 - 1000,car,0.011058642330257716 +,,,,,no,,0 - 1000,pt,0.005486344364754511 +,,,,,no,,0 - 1000,ride,0.005508506862313705 +,,,,,no,,0 - 1000,walk,0.23710900428666265 +,,,,,no,,1000 - 2000,bike,0.0075046230570577285 +,,,,,no,,1000 - 2000,car,0.024841272479000517 +,,,,,no,,1000 - 2000,pt,0.032970722587121595 +,,,,,no,,1000 - 2000,ride,0.008363099312115381 +,,,,,no,,1000 - 2000,walk,0.0599070025225657 +,,,,,no,,2000 - 5000,bike,0.0030356396566586616 +,,,,,no,,2000 - 5000,car,0.054358518223715485 +,,,,,no,,2000 - 5000,pt,0.11169934543132519 +,,,,,no,,2000 - 5000,ride,0.01979098826873427 +,,,,,no,,2000 - 5000,walk,0.014730110159550966 +,,,,,no,,5000 - 10000,bike,0.001725264051395107 +,,,,,no,,5000 - 10000,car,0.05595315897447628 +,,,,,no,,5000 - 10000,pt,0.11104574218043785 +,,,,,no,,5000 - 10000,ride,0.01866749245961559 +,,,,,no,,5000 - 10000,walk,0.0009527341528081855 +,,,,,no,,10000 - 20000,bike,0.00010772404870398959 +,,,,,no,,10000 - 20000,car,0.04708939066977534 +,,,,,no,,10000 - 20000,pt,0.08253975507167001 +,,,,,no,,10000 - 20000,ride,0.013133193905086457 +,,,,,no,,10000 - 20000,walk,0.00011342697602954677 +,,,,,no,,20000+,bike,0.0 +,,,,,no,,20000+,car,0.030346166956882623 +,,,,,no,,20000+,pt,0.030469023737508497 +,,,,,no,,20000+,ride,0.007204882433134221 +,,,,,no,,20000+,walk,0.00038699863895556185 +,,,,,unknown,,0 - 1000,bike,0.02196874356601975 +,,,,,unknown,,0 - 1000,car,0.008865973925200346 +,,,,,unknown,,0 - 1000,pt,0.008820595658083127 +,,,,,unknown,,0 - 1000,ride,0.007825311269823323 +,,,,,unknown,,0 - 1000,walk,0.30926153323029304 +,,,,,unknown,,1000 - 2000,bike,0.019899996897947855 +,,,,,unknown,,1000 - 2000,car,0.02204018573997509 +,,,,,unknown,,1000 - 2000,pt,0.03890621393413655 +,,,,,unknown,,1000 - 2000,ride,0.020312569496132766 +,,,,,unknown,,1000 - 2000,walk,0.06940078290988638 +,,,,,unknown,,2000 - 5000,bike,0.010804071418919524 +,,,,,unknown,,2000 - 5000,car,0.0421588846988312 +,,,,,unknown,,2000 - 5000,pt,0.09282163808907376 +,,,,,unknown,,2000 - 5000,ride,0.033425026327763716 +,,,,,unknown,,2000 - 5000,walk,0.014267811127275818 +,,,,,unknown,,5000 - 10000,bike,0.0016996109909697531 +,,,,,unknown,,5000 - 10000,car,0.04014865505890483 +,,,,,unknown,,5000 - 10000,pt,0.07910634210857279 +,,,,,unknown,,5000 - 10000,ride,0.018465090116188528 +,,,,,unknown,,5000 - 10000,walk,0.0018455430781873152 +,,,,,unknown,,10000 - 20000,bike,0.00011814971112985102 +,,,,,unknown,,10000 - 20000,car,0.03155496878441373 +,,,,,unknown,,10000 - 20000,pt,0.04959157127841764 +,,,,,unknown,,10000 - 20000,ride,0.011563902896211451 +,,,,,unknown,,10000 - 20000,walk,0.0012595592388616737 +,,,,,unknown,,20000+,bike,0.0 +,,,,,unknown,,20000+,car,0.01955813542665283 +,,,,,unknown,,20000+,pt,0.019790102528474483 +,,,,,unknown,,20000+,ride,0.004324709516604467 +,,,,,unknown,,20000+,walk,0.00019432097704832695 +,,,,,,yes,0 - 1000,bike,0.026877735272587144 +,,,,,,yes,0 - 1000,car,0.004609738415460028 +,,,,,,yes,0 - 1000,pt,0.0056114180907021445 +,,,,,,yes,0 - 1000,ride,0.002009607976268384 +,,,,,,yes,0 - 1000,walk,0.2170462833410825 +,,,,,,yes,1000 - 2000,bike,0.0313461177979366 +,,,,,,yes,1000 - 2000,car,0.011886217774270638 +,,,,,,yes,1000 - 2000,pt,0.03268994045589096 +,,,,,,yes,1000 - 2000,ride,0.004885892680583904 +,,,,,,yes,1000 - 2000,walk,0.049037458291423135 +,,,,,,yes,2000 - 5000,bike,0.04151569490188568 +,,,,,,yes,2000 - 5000,car,0.02526366671773831 +,,,,,,yes,2000 - 5000,pt,0.11828715810574975 +,,,,,,yes,2000 - 5000,ride,0.013319548343406455 +,,,,,,yes,2000 - 5000,walk,0.012645027907752997 +,,,,,,yes,5000 - 10000,bike,0.02000968548822276 +,,,,,,yes,5000 - 10000,car,0.02496105021929467 +,,,,,,yes,5000 - 10000,pt,0.14380745218170304 +,,,,,,yes,5000 - 10000,ride,0.009869154521698925 +,,,,,,yes,5000 - 10000,walk,0.0015995635368503834 +,,,,,,yes,10000 - 20000,bike,0.0051539763386626285 +,,,,,,yes,10000 - 20000,car,0.018507075295630705 +,,,,,,yes,10000 - 20000,pt,0.11421724799309253 +,,,,,,yes,10000 - 20000,ride,0.006420798868946036 +,,,,,,yes,10000 - 20000,walk,0.0005065512201440519 +,,,,,,yes,20000+,bike,0.00045032754383129167 +,,,,,,yes,20000+,car,0.011373225663843514 +,,,,,,yes,20000+,pt,0.042587774643345226 +,,,,,,yes,20000+,ride,0.0032529008112295254 +,,,,,,yes,20000+,walk,0.0002517096007660357 +,,,,,,no,0 - 1000,bike,0.049203787113537144 +,,,,,,no,0 - 1000,car,0.017181996770518737 +,,,,,,no,0 - 1000,pt,0.0014676064701637842 +,,,,,,no,0 - 1000,ride,0.0037388193898886695 +,,,,,,no,0 - 1000,walk,0.22834165520840394 +,,,,,,no,1000 - 2000,bike,0.060967425010797105 +,,,,,,no,1000 - 2000,car,0.03802701934901353 +,,,,,,no,1000 - 2000,pt,0.0058198328118644485 +,,,,,,no,1000 - 2000,ride,0.010726983971031032 +,,,,,,no,1000 - 2000,walk,0.051980322210022724 +,,,,,,no,2000 - 5000,bike,0.07422011095918414 +,,,,,,no,2000 - 5000,car,0.08628816036798163 +,,,,,,no,2000 - 5000,pt,0.020461006554586395 +,,,,,,no,2000 - 5000,ride,0.019297423684489126 +,,,,,,no,2000 - 5000,walk,0.01468571778797613 +,,,,,,no,5000 - 10000,bike,0.036985386950286615 +,,,,,,no,5000 - 10000,car,0.08769873514183764 +,,,,,,no,5000 - 10000,pt,0.021860522255242685 +,,,,,,no,5000 - 10000,ride,0.015098401866535355 +,,,,,,no,5000 - 10000,walk,0.0014461399829227438 +,,,,,,no,10000 - 20000,bike,0.010107370860379439 +,,,,,,no,10000 - 20000,car,0.07086755599846502 +,,,,,,no,10000 - 20000,pt,0.01524471883856497 +,,,,,,no,10000 - 20000,ride,0.007864588085845437 +,,,,,,no,10000 - 20000,walk,0.0005447331264401868 +,,,,,,no,20000+,bike,0.0009236480015640312 +,,,,,,no,20000+,car,0.036775023310245354 +,,,,,,no,20000+,pt,0.007645033418080438 +,,,,,,no,20000+,ride,0.0043792848052615985 +,,,,,,no,20000+,walk,0.00015098969886978098 +,,,,,,unknown,0 - 1000,bike,0.08452578986227006 +,,,,,,unknown,0 - 1000,car,6.486504146205827e-05 +,,,,,,unknown,0 - 1000,pt,0.002175395846041459 +,,,,,,unknown,0 - 1000,ride,0.021684233861045763 +,,,,,,unknown,0 - 1000,walk,0.34731038526891134 +,,,,,,unknown,1000 - 2000,bike,0.07464418315020238 +,,,,,,unknown,1000 - 2000,car,0.000316211749246317 +,,,,,,unknown,1000 - 2000,pt,0.02428926385010706 +,,,,,,unknown,1000 - 2000,ride,0.05746270563561737 +,,,,,,unknown,1000 - 2000,walk,0.08500884635887647 +,,,,,,unknown,2000 - 5000,bike,0.03960672674551991 +,,,,,,unknown,2000 - 5000,car,0.0 +,,,,,,unknown,2000 - 5000,pt,0.04812409458714954 +,,,,,,unknown,2000 - 5000,ride,0.08632711015520911 +,,,,,,unknown,2000 - 5000,walk,0.01129386656497147 +,,,,,,unknown,5000 - 10000,bike,0.004812536200797506 +,,,,,,unknown,5000 - 10000,car,0.00044170618535102315 +,,,,,,unknown,5000 - 10000,pt,0.024705916309625302 +,,,,,,unknown,5000 - 10000,ride,0.043816530547673184 +,,,,,,unknown,5000 - 10000,walk,0.0013550153438346378 +,,,,,,unknown,10000 - 20000,bike,0.0 +,,,,,,unknown,10000 - 20000,car,0.0008468921174164319 +,,,,,,unknown,10000 - 20000,pt,0.006606733358829172 +,,,,,,unknown,10000 - 20000,ride,0.023100724541931888 +,,,,,,unknown,10000 - 20000,walk,0.0005742755841204714 +,,,,,,unknown,20000+,bike,0.0 +,,,,,,unknown,20000+,car,0.0 +,,,,,,unknown,20000+,pt,0.003862007367512052 +,,,,,,unknown,20000+,ride,0.006800708909865809 +,,,,,,unknown,20000+,walk,0.00024327485641202528 From 85c2904cf983f0752ee90d33da8e013c3d5155b0 Mon Sep 17 00:00:00 2001 From: rakow Date: Tue, 11 Jun 2024 20:51:44 +0200 Subject: [PATCH 17/31] fix pivot usage --- .../java/org/matsim/simwrapper/dashboard/TripDashboard.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index 1ab7dee2244..fb62854d9e5 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -369,14 +369,13 @@ private void createGroupedTab(Layout layout, String[] args) { viz.title = "Mode share"; viz.description = "by " + cat; viz.layout = tech.tablesaw.plotly.components.Layout.builder() - .xAxis(Axis.builder().title("dist_group").build()) - .yAxis(Axis.builder().title("sim_share").build()) + .xAxis(Axis.builder().title("share").build()) .barMode(tech.tablesaw.plotly.components.Layout.BarMode.STACK) .build(); // TODO: Still in testing Plotly.DataMapping ds = viz.addDataset(data.computeWithPlaceholder(TripAnalysis.class, "mode_share_per_%s.csv", cat)) - .pivot(List.of("main_mode"), "source", "share") + .pivot(List.of("main_mode", "dist_group", cat), "source", "share") .aggregate(List.of("main_mode", "source", cat), "share", Plotly.AggrFunc.SUM) .mapping() .facetCol(cat) From dc2ee50d2d38d1c43dc4318aadca1855cf365d20 Mon Sep 17 00:00:00 2001 From: rakow Date: Tue, 11 Jun 2024 21:19:22 +0200 Subject: [PATCH 18/31] rename entries --- .../java/org/matsim/simwrapper/dashboard/TripDashboard.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index fb62854d9e5..1900daa6c34 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -377,6 +377,8 @@ private void createGroupedTab(Layout layout, String[] args) { Plotly.DataMapping ds = viz.addDataset(data.computeWithPlaceholder(TripAnalysis.class, "mode_share_per_%s.csv", cat)) .pivot(List.of("main_mode", "dist_group", cat), "source", "share") .aggregate(List.of("main_mode", "source", cat), "share", Plotly.AggrFunc.SUM) + .rename("sim_share", "Sim") + .rename("ref_share", "Ref") .mapping() .facetCol(cat) .name("main_mode") From cab0b80d9cbfc0da9cc3b824ddd05b30fd755636 Mon Sep 17 00:00:00 2001 From: rakow Date: Tue, 11 Jun 2024 21:51:58 +0200 Subject: [PATCH 19/31] add plots for distance distributions --- .../analysis/population/TripAnalysis.java | 2 +- .../population/TripByGroupAnalysis.java | 4 +- .../simwrapper/dashboard/TripDashboard.java | 74 +++++++++++++------ 3 files changed, 55 insertions(+), 25 deletions(-) diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java index ef2c70e726a..fb21949caf3 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java @@ -217,7 +217,7 @@ public Integer call() throws Exception { writeModeShare(joined, labels); if (groups != null) { - groups.analyzeModeShare(joined, labels, (g) -> output.getPath("mode_share_per_%s.csv", g)); + groups.analyzeModeShare(joined, labels, modeOrder, (g) -> output.getPath("mode_share_per_%s.csv", g)); } if (persons.containsColumn(ATTR_REF_MODES)) { diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java index 7a0787767cf..47edfec0824 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripByGroupAnalysis.java @@ -84,7 +84,7 @@ final class TripByGroupAnalysis { } } - void analyzeModeShare(Table trips, List dists, Function output) { + void analyzeModeShare(Table trips, List dists, List modeOrder, Function output) { for (Group group : groups) { @@ -101,7 +101,7 @@ void analyzeModeShare(Table trips, List dists, Function ou // Sort by dist_group and mode Comparator cmp = Comparator.comparingInt(row -> dists.indexOf(row.getString("dist_group"))); - aggr = aggr.sortOn(cmp.thenComparing(row -> row.getString("main_mode"))); + aggr = aggr.sortOn(cmp.thenComparingInt(row -> modeOrder.indexOf(row.getString("main_mode")))); // Norm each group to 1 String norm = group.columns.get(0); diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index 1900daa6c34..a248f9c298d 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -364,32 +364,62 @@ private void createGroupedTab(Layout layout, String[] args) { for (String cat : Objects.requireNonNull(categories, "Categories not set")) { - layout.row("category_" + cat, "By Groups").el(Plotly.class, (viz, data) -> { + layout.row("category_" + cat, "By Groups") + .el(Plotly.class, (viz, data) -> { + + viz.title = "Mode share"; + viz.description = "by " + cat; + viz.layout = tech.tablesaw.plotly.components.Layout.builder() + .xAxis(Axis.builder().title("share").build()) + .barMode(tech.tablesaw.plotly.components.Layout.BarMode.STACK) + .build(); + + Plotly.DataMapping ds = viz.addDataset(data.computeWithPlaceholder(TripAnalysis.class, "mode_share_per_%s.csv", cat)) + .pivot(List.of("main_mode", "dist_group", cat), "source", "share") + .aggregate(List.of("main_mode", "source", cat), "share", Plotly.AggrFunc.SUM) + .rename("sim_share", "Sim") + .rename("ref_share", "Ref") + .mapping() + .facetCol(cat) + .name("main_mode") + .x("source") + .y("share"); + + + viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT) + .orientation(BarTrace.Orientation.VERTICAL) + .build(), ds); + + }).el(Plotly.class, (viz, data) -> { + viz.title = "Modal distance distribution"; + viz.description = "by " + cat; + viz.layout = tech.tablesaw.plotly.components.Layout.builder() + .xAxis(Axis.builder().title("Distance group").build()) + .yAxis(Axis.builder().title("Share").build()) + .barMode(tech.tablesaw.plotly.components.Layout.BarMode.STACK) + .build(); + + // TODO: should use facet row, but does not work yet + // TODO: hard to see, because too many bars + Plotly.DataMapping ds = viz.addDataset(data.computeWithPlaceholder(TripAnalysis.class, "mode_share_per_%s.csv", cat)) + .pivot(List.of("main_mode", "dist_group", cat), "source", "share") + .normalize(List.of("dist_group", "source", cat), "share") + .rename("sim_share", "Sim") + .rename("ref_share", "Ref") + .mapping() + .facetCol(cat) + .name("main_mode") + .x("dist_group") + .y("share"); - viz.title = "Mode share"; - viz.description = "by " + cat; - viz.layout = tech.tablesaw.plotly.components.Layout.builder() - .xAxis(Axis.builder().title("share").build()) - .barMode(tech.tablesaw.plotly.components.Layout.BarMode.STACK) - .build(); + viz.multiIndex = Map.of("dist_group", "source"); - // TODO: Still in testing - Plotly.DataMapping ds = viz.addDataset(data.computeWithPlaceholder(TripAnalysis.class, "mode_share_per_%s.csv", cat)) - .pivot(List.of("main_mode", "dist_group", cat), "source", "share") - .aggregate(List.of("main_mode", "source", cat), "share", Plotly.AggrFunc.SUM) - .rename("sim_share", "Sim") - .rename("ref_share", "Ref") - .mapping() - .facetCol(cat) - .name("main_mode") - .x("share") - .y("source"); + viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT) + .orientation(BarTrace.Orientation.VERTICAL) + .build(), ds); - viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT) - .orientation(BarTrace.Orientation.HORIZONTAL) - .build(), ds); - }); + }); } } From 45104dd437ad2766032b35cc99c91aca6f2cebdb Mon Sep 17 00:00:00 2001 From: rakow Date: Tue, 11 Jun 2024 21:53:19 +0200 Subject: [PATCH 20/31] add todo --- .../java/org/matsim/simwrapper/dashboard/TripDashboard.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index a248f9c298d..37f1fb4a11b 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -400,7 +400,7 @@ private void createGroupedTab(Layout layout, String[] args) { .build(); // TODO: should use facet row, but does not work yet - // TODO: hard to see, because too many bars + // TODO: hard to see, because too many bars, maybe try dropdown (instead of facet) Plotly.DataMapping ds = viz.addDataset(data.computeWithPlaceholder(TripAnalysis.class, "mode_share_per_%s.csv", cat)) .pivot(List.of("main_mode", "dist_group", cat), "source", "share") .normalize(List.of("dist_group", "source", cat), "share") From 6a48a0578fa7883054299fc6aebdd69491d8ebf7 Mon Sep 17 00:00:00 2001 From: rakow Date: Tue, 11 Jun 2024 22:26:50 +0200 Subject: [PATCH 21/31] use dropdown for plots --- .../org/matsim/simwrapper/dashboard/TripDashboard.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index 37f1fb4a11b..2ab585eb4eb 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -369,6 +369,7 @@ private void createGroupedTab(Layout layout, String[] args) { viz.title = "Mode share"; viz.description = "by " + cat; + viz.height = 6d; viz.layout = tech.tablesaw.plotly.components.Layout.builder() .xAxis(Axis.builder().title("share").build()) .barMode(tech.tablesaw.plotly.components.Layout.BarMode.STACK) @@ -399,16 +400,17 @@ private void createGroupedTab(Layout layout, String[] args) { .barMode(tech.tablesaw.plotly.components.Layout.BarMode.STACK) .build(); - // TODO: should use facet row, but does not work yet - // TODO: hard to see, because too many bars, maybe try dropdown (instead of facet) + viz.interactive = Plotly.Interactive.dropdown; + + // TODO: modes are not separated into different traces + // probably dropdown config in plotly needs to be extended Plotly.DataMapping ds = viz.addDataset(data.computeWithPlaceholder(TripAnalysis.class, "mode_share_per_%s.csv", cat)) .pivot(List.of("main_mode", "dist_group", cat), "source", "share") .normalize(List.of("dist_group", "source", cat), "share") .rename("sim_share", "Sim") .rename("ref_share", "Ref") .mapping() - .facetCol(cat) - .name("main_mode") + .name(cat) .x("dist_group") .y("share"); From e6000b604d6afda71bbb99efdda79d4d2b8a7dd0 Mon Sep 17 00:00:00 2001 From: rakow Date: Wed, 12 Jun 2024 20:51:47 +0200 Subject: [PATCH 22/31] add cohen kappa to evaluation --- .../population/TripChoiceAnalysis.java | 70 +++++++++------ .../population/TripChoiceAnalysisTest.java | 87 +++++++++++++++++++ .../simwrapper/dashboard/TripDashboard.java | 26 +++--- 3 files changed, 146 insertions(+), 37 deletions(-) create mode 100644 contribs/application/src/test/java/org/matsim/application/analysis/population/TripChoiceAnalysisTest.java diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java index 4857359d854..2ae351df200 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java @@ -18,6 +18,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.*; +import java.util.stream.IntStream; /** * Helper class to analyze trip choices from persons against reference data. @@ -42,6 +43,7 @@ final class TripChoiceAnalysis { * Contains predication result for each mode. */ private final Map counts = new HashMap<>(); + private final Object2DoubleMap pairs = new Object2DoubleOpenHashMap<>(); /** * Contains confusion matrix for each mode. @@ -73,26 +75,19 @@ public TripChoiceAnalysis(Table persons, Table trips, List modeOrder) { log.warn("Person {} trip {} does not match ref data ({})", person, n, split.length); } + for (Entry e : data) { + pairs.mergeDouble(new Pair(e.trueMode(), e.predMode()), e.weight(), Double::sum); + } + for (String mode : modeOrder) { counts.put(mode, countPredictions(mode, data)); DoubleArrayList preds = new DoubleArrayList(); for (String predMode : modeOrder) { - double c = 0; - for (Entry e : data) { - if (!e.trueMode.equals(mode)) - continue; - if (e.predMode.equals(predMode)) - c += e.weight; - } + double c = pairs.getDouble(new Pair(mode, predMode)); preds.add(c); } - double sum = preds.doubleStream().sum(); - for (int i = 0; i < preds.size(); i++) { - preds.set(i, preds.getDouble(i) / sum); - } - confusionMatrix.add(preds); } } @@ -116,6 +111,35 @@ private static double f1(Counts c) { return 2 * c.tp / (2 * c.tp + c.fp + c.fn); } + /** + * Implemented as in sklearn.metrics.cohen_kappa_score. + * See Wikipedia + * + * @param cm confusion matrix + */ + static double computeCohenKappa(List cm) { + DoubleList sumRows = cm.stream().mapToDouble(l -> l.doubleStream().sum()).collect(DoubleArrayList::new, DoubleList::add, DoubleList::addAll); + DoubleList sumCols = IntStream.range(0, cm.size()).mapToDouble(i -> cm.stream().mapToDouble(l -> l.getDouble(i)).sum()).collect(DoubleArrayList::new, DoubleList::add, DoubleList::addAll); + double sumTotal = sumRows.doubleStream().sum(); + double expected = 0; + + for (int i = 0; i < cm.size(); i++) { + for (int j = 0; j < cm.size(); j++) { + if (i != j) + expected += sumRows.getDouble(i) * sumCols.getDouble(j) / sumTotal; + } + } + + double k = 0; + for (int i = 0; i < cm.size(); i++) { + for (int j = 0; j < cm.size(); j++) { + if (i != j) + k += cm.get(i).getDouble(j) / expected; + } + } + return 1 - k; + } + private Counts countPredictions(String mode, List data) { double tp = 0, fp = 0, fn = 0, tn = 0; double total = 0; @@ -180,9 +204,8 @@ public void writeChoiceEvaluation(Path path) throws IOException { csv.printRecord("Recall (macro avg.)", round(recall.orElse(0))); csv.printRecord("F1 Score (micro avg.)", round(2 * tp / (tpfp + tpfn))); csv.printRecord("F1 Score (macro avg.)", round(f1.orElse(0))); + csv.printRecord("Cohen’s Kappa", round(computeCohenKappa(confusionMatrix))); } - - // TODO Cohen’s Kappa, Mathews Correlation Coefficient (MCC) } /** @@ -211,7 +234,7 @@ public void writeChoiceEvaluationPerMode(Path path) throws IOException { } /** - * Write confusion matrix. + * Write confusion matrix. This normalizes per row. */ public void writeConfusionMatrix(Path path) throws IOException { try (CSVPrinter csv = new CSVPrinter(Files.newBufferedWriter(path), CSVFormat.DEFAULT)) { @@ -224,8 +247,9 @@ public void writeConfusionMatrix(Path path) throws IOException { for (int i = 0; i < modeOrder.size(); i++) { csv.print(modeOrder.get(i)); DoubleList row = confusionMatrix.get(i); + double sum = row.doubleStream().sum(); for (int j = 0; j < row.size(); j++) { - csv.print(row.getDouble(j)); + csv.print(row.getDouble(j) / sum); } csv.println(); } @@ -234,17 +258,11 @@ public void writeConfusionMatrix(Path path) throws IOException { public void writeModePredictionError(Path path) throws IOException { - Object2DoubleMap counts = new Object2DoubleOpenHashMap<>(); - // inefficient, should not be used on large datasets - for (Entry e : data) { - counts.mergeDouble(new Pair(e.trueMode(), e.predMode()), e.weight(), Double::sum); - } - try (CSVPrinter csv = new CSVPrinter(Files.newBufferedWriter(path), CSVFormat.DEFAULT)) { csv.printRecord("true_mode", "predicted_mode", "count"); for (String trueMode : modeOrder) { for (String predMode : modeOrder) { - double c = counts.getDouble(new Pair(trueMode, predMode)); + double c = pairs.getDouble(new Pair(trueMode, predMode)); if (c > 0) csv.printRecord(trueMode, predMode, c); } @@ -252,10 +270,10 @@ public void writeModePredictionError(Path path) throws IOException { } } - private record Entry(String person, double weight, int n, String trueMode, String predMode) { + record Entry(String person, double weight, int n, String trueMode, String predMode) { } - private record Pair(String trueMode, String predMode) { + record Pair(String trueMode, String predMode) { } /** @@ -266,7 +284,7 @@ private record Pair(String trueMode, String predMode) { * @param fn incorrectly predicted different class * @param tn correctly predicated different class */ - private record Counts(double tp, double fp, double fn, double tn, double total) { + record Counts(double tp, double fp, double fn, double tn, double total) { } diff --git a/contribs/application/src/test/java/org/matsim/application/analysis/population/TripChoiceAnalysisTest.java b/contribs/application/src/test/java/org/matsim/application/analysis/population/TripChoiceAnalysisTest.java new file mode 100644 index 00000000000..bbc75c738a6 --- /dev/null +++ b/contribs/application/src/test/java/org/matsim/application/analysis/population/TripChoiceAnalysisTest.java @@ -0,0 +1,87 @@ +package org.matsim.application.analysis.population; + +import it.unimi.dsi.fastutil.doubles.DoubleArrayList; +import it.unimi.dsi.fastutil.doubles.DoubleList; +import it.unimi.dsi.fastutil.objects.Object2DoubleMap; +import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap; +import org.assertj.core.data.Offset; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class TripChoiceAnalysisTest { + + /** + * Create confusion matrix. + */ + public static List cm(String... entries) { + List rows = new ArrayList<>(); + List distinct = Arrays.stream(entries).distinct().toList(); + Object2DoubleMap pairs = new Object2DoubleOpenHashMap<>(); + + for (int i = 0; i < entries.length; i += 2) { + TripChoiceAnalysis.Pair pair = new TripChoiceAnalysis.Pair(entries[i], entries[i + 1]); + pairs.mergeDouble(pair, 1, Double::sum); + } + + for (String d1 : distinct) { + DoubleArrayList row = new DoubleArrayList(); + for (String d2 : distinct) { + row.add(pairs.getDouble(new TripChoiceAnalysis.Pair(d1, d2))); + } + rows.add(row); + } + + return rows; + } + + @Test + void cohenKappa() { + + double ck = TripChoiceAnalysis.computeCohenKappa(List.of()); + assertThat(ck).isEqualTo(1); + + ck = TripChoiceAnalysis.computeCohenKappa(cm( + "a", "a", + "b", "b", + "b", "b", + "c", "c") + ); + + assertThat(ck).isEqualTo(1.0); + ck = TripChoiceAnalysis.computeCohenKappa(cm( + "a", "c", + "d", "e", + "a", "b", + "b", "d" + )); + + assertThat(ck).isLessThan(0.0); + + // These have been verified with sklearn + ck = TripChoiceAnalysis.computeCohenKappa(cm( + "negative", "negative", + "positive", "positive", + "negative", "negative", + "neutral", "neutral", + "positive", "negative" + )); + + assertThat(ck).isEqualTo(0.6875); + + ck = TripChoiceAnalysis.computeCohenKappa(cm( + "negative", "positive", + "positive", "neutral", + "negative", "negative", + "neutral", "neutral", + "positive", "negative" + )); + + assertThat(ck).isEqualTo( 0.11764705882352955, Offset.offset(1e-5)); + + } +} diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index 2ab585eb4eb..21cb7eaa951 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -308,20 +308,25 @@ public void configure(Header header, Layout layout) { private void createChoiceTab(Layout layout, String[] args) { layout.row("choice-intro", "Mode Choice").el(TextBlock.class, (viz, data) -> { - viz.title = "Introduction"; + viz.title = "Information"; viz.content = """ - Information regarding the metrics used: - - Precision is the fraction of instances correctly classified as belonging to a specific class out of all instances the model predicted to belong to that class. - Recall is the fraction of instances in a class that the model correctly classified out of all instances in that class. - The macro-average computes the metric independently for each class and then take the average (hence treating all classes equally). - The micro-average will aggregate the contributions of all classes to compute the average metric."""; + Note that these metrics are based on a single run and may have limited interpretability. For a more robust evaluation, consider running multiple simulations with different seeds and use metrics that consider probabilities as well. + (log-likelihood, Brier score, etc.) + For policy cases, these metrics do not have any meaning. They are solely for the base-case. + + - Precision is the fraction of instances correctly classified as belonging to a specific class out of all instances the model predicted to belong to that class. + - Recall is the fraction of instances in a class that the model correctly classified out of all instances in that class. + - The macro-average computes the metric independently for each class and then take the average (hence treating all classes equally). + - The micro-average will aggregate the contributions of all classes to compute the average metric. + - Cohen's Kappa is a measure of agreement between two raters that corrects for chance agreement. 1.0 indicates perfect agreement, 0.0 or less indicates agreement by chance. + """; }); layout.row("choice", "Mode Choice").el(Table.class, (viz, data) -> { viz.title = "Choice Evaluation"; viz.description = "Metrics for mode choice."; viz.showAllRows = true; + viz.height = 6d; viz.dataset = data.compute(TripAnalysis.class, "mode_choice_evaluation.csv", args); }); @@ -334,7 +339,7 @@ private void createChoiceTab(Layout layout, String[] args) { layout.row("choice-plots", "Mode Choice").el(Heatmap.class, (viz, data) -> { viz.title = "Confusion Matrix"; - viz.description = "Confusion matrix for mode choice."; + viz.description = "Share of (mis)classified modes."; viz.xAxisTitle = "Predicted"; viz.yAxisTitle = "True"; viz.dataset = data.compute(TripAnalysis.class, "mode_confusion_matrix.csv", args); @@ -371,7 +376,6 @@ private void createGroupedTab(Layout layout, String[] args) { viz.description = "by " + cat; viz.height = 6d; viz.layout = tech.tablesaw.plotly.components.Layout.builder() - .xAxis(Axis.builder().title("share").build()) .barMode(tech.tablesaw.plotly.components.Layout.BarMode.STACK) .build(); @@ -410,7 +414,8 @@ private void createGroupedTab(Layout layout, String[] args) { .rename("sim_share", "Sim") .rename("ref_share", "Ref") .mapping() - .name(cat) + .name("main_mode") + .facetCol(cat) .x("dist_group") .y("share"); @@ -420,7 +425,6 @@ private void createGroupedTab(Layout layout, String[] args) { .orientation(BarTrace.Orientation.VERTICAL) .build(), ds); - }); } From 16998d394f0a0467a546f10b97a29c9655387b51 Mon Sep 17 00:00:00 2001 From: rakow Date: Thu, 13 Jun 2024 19:44:02 +0200 Subject: [PATCH 23/31] output euclidean distance for trip choices --- .../application/analysis/population/TripAnalysis.java | 1 + .../analysis/population/TripChoiceAnalysis.java | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java index fb21949caf3..93acd1b90ec 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java @@ -146,6 +146,7 @@ public Integer call() throws Exception { // Map.of only has 10 argument max columnTypes.put("traveled_distance", ColumnType.LONG); + columnTypes.put("euclidean_distance", ColumnType.LONG); Table trips = Table.read().csv(CsvReadOptions.builder(IOUtils.getBufferedReader(input.getPath("trips.csv"))) .columnTypesPartial(columnTypes) diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java index 2ae351df200..09b46222618 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java @@ -70,7 +70,7 @@ public TripChoiceAnalysis(Table persons, Table trips, List modeOrder) { if (n < split.length) { String trueMode = split[n]; - data.add(new Entry(person, weight, n, trueMode, predMode)); + data.add(new Entry(person, weight, n, trip.getLong("euclidean_distance"), trueMode, predMode)); } else log.warn("Person {} trip {} does not match ref data ({})", person, n, split.length); } @@ -166,9 +166,9 @@ private Counts countPredictions(String mode, List data) { */ public void writeChoices(Path path) throws IOException { try (CSVPrinter csv = new CSVPrinter(Files.newBufferedWriter(path), CSVFormat.DEFAULT)) { - csv.printRecord("person", "weight", "n", "true_mode", "pred_mode"); + csv.printRecord("person", "weight", "n", "euclidean_distance", "true_mode", "pred_mode"); for (Entry e : data) { - csv.printRecord(e.person, e.weight, e.n, e.trueMode, e.predMode); + csv.printRecord(e.person, e.weight, e.n, e.dist, e.trueMode, e.predMode); } } } @@ -270,7 +270,7 @@ public void writeModePredictionError(Path path) throws IOException { } } - record Entry(String person, double weight, int n, String trueMode, String predMode) { + record Entry(String person, double weight, int n, long dist, String trueMode, String predMode) { } record Pair(String trueMode, String predMode) { From d04715c0051bcc5256da6d90cadf961cbba310a3 Mon Sep 17 00:00:00 2001 From: rakow Date: Thu, 13 Jun 2024 21:05:30 +0200 Subject: [PATCH 24/31] add ref_id attribute --- .../application/analysis/population/TripAnalysis.java | 6 ++++++ .../analysis/population/TripChoiceAnalysis.java | 9 +++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java index 93acd1b90ec..4da59a48294 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripAnalysis.java @@ -48,6 +48,11 @@ public class TripAnalysis implements MATSimAppCommand { private static final Logger log = LogManager.getLogger(TripAnalysis.class); + + /** + * Attributes which relates this person to a reference person. + */ + public static String ATTR_REF_ID = "ref_id"; /** * Person attribute that contains the reference modes of a person. Multiple modes are delimited by "-". */ @@ -56,6 +61,7 @@ public class TripAnalysis implements MATSimAppCommand { * Person attribute containing its weight for analysis purposes. */ public static String ATTR_REF_WEIGHT = "ref_weight"; + @CommandLine.Mixin private InputOptions input = InputOptions.ofCommand(TripAnalysis.class); @CommandLine.Mixin diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java index 09b46222618..81909aa0f6d 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java @@ -70,7 +70,8 @@ public TripChoiceAnalysis(Table persons, Table trips, List modeOrder) { if (n < split.length) { String trueMode = split[n]; - data.add(new Entry(person, weight, n, trip.getLong("euclidean_distance"), trueMode, predMode)); + data.add(new Entry((String) trip.getObject(TripAnalysis.ATTR_REF_ID), + person, weight, n, trip.getLong("euclidean_distance"), trueMode, predMode)); } else log.warn("Person {} trip {} does not match ref data ({})", person, n, split.length); } @@ -166,9 +167,9 @@ private Counts countPredictions(String mode, List data) { */ public void writeChoices(Path path) throws IOException { try (CSVPrinter csv = new CSVPrinter(Files.newBufferedWriter(path), CSVFormat.DEFAULT)) { - csv.printRecord("person", "weight", "n", "euclidean_distance", "true_mode", "pred_mode"); + csv.printRecord("ref_id", "person", "weight", "n", "euclidean_distance", "true_mode", "pred_mode"); for (Entry e : data) { - csv.printRecord(e.person, e.weight, e.n, e.dist, e.trueMode, e.predMode); + csv.printRecord(e.refId, e.person, e.weight, e.n, e.dist, e.trueMode, e.predMode); } } } @@ -270,7 +271,7 @@ public void writeModePredictionError(Path path) throws IOException { } } - record Entry(String person, double weight, int n, long dist, String trueMode, String predMode) { + record Entry(String refId, String person, double weight, int n, long dist, String trueMode, String predMode) { } record Pair(String trueMode, String predMode) { From d374a81d1674b2ac0a76c1c1c683d07b2a688cae Mon Sep 17 00:00:00 2001 From: frievoe97 Date: Mon, 17 Jun 2024 15:22:55 +0200 Subject: [PATCH 25/31] added heatmap to TripDashboard --- .../java/org/matsim/simwrapper/dashboard/TripDashboard.java | 2 ++ .../src/test/java/org/matsim/simwrapper/TestScenario.java | 1 + 2 files changed, 3 insertions(+) diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index 21cb7eaa951..8ecaef57fdf 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -342,6 +342,8 @@ private void createChoiceTab(Layout layout, String[] args) { viz.description = "Share of (mis)classified modes."; viz.xAxisTitle = "Predicted"; viz.yAxisTitle = "True"; + viz.y = "True/Pred"; + viz.flipAxes = "True"; viz.dataset = data.compute(TripAnalysis.class, "mode_confusion_matrix.csv", args); }); diff --git a/contribs/simwrapper/src/test/java/org/matsim/simwrapper/TestScenario.java b/contribs/simwrapper/src/test/java/org/matsim/simwrapper/TestScenario.java index 55aa8d54418..0ba046d7d05 100644 --- a/contribs/simwrapper/src/test/java/org/matsim/simwrapper/TestScenario.java +++ b/contribs/simwrapper/src/test/java/org/matsim/simwrapper/TestScenario.java @@ -97,6 +97,7 @@ else if (r < 0.3) List trips = TripStructureUtils.getTrips(person.getSelectedPlan()); String ref = trips.stream().map(genMode).collect(Collectors.joining("-")); + person.getAttributes().putAttribute(TripAnalysis.ATTR_REF_ID, person.getId().toString()); person.getAttributes().putAttribute(TripAnalysis.ATTR_REF_MODES, ref); } } From 46230108da767ccde94f5a5f69d9c43e171def24 Mon Sep 17 00:00:00 2001 From: rakow Date: Mon, 17 Jun 2024 16:48:27 +0200 Subject: [PATCH 26/31] update description --- .../application/analysis/population/TripChoiceAnalysis.java | 3 --- .../java/org/matsim/simwrapper/dashboard/TripDashboard.java | 5 ++--- .../src/main/java/org/matsim/simwrapper/viz/Heatmap.java | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java index 81909aa0f6d..cb6f2a4b151 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java @@ -199,11 +199,8 @@ public void writeChoiceEvaluation(Path path) throws IOException { csv.printRecord("Info", "Value"); csv.printRecord("Accuracy", round(tp / total)); - csv.printRecord("Precision (micro avg.)", round(tp / tpfp)); csv.printRecord("Precision (macro avg.)", round(precision.orElse(0))); - csv.printRecord("Recall (micro avg.)", round(tp / tpfn)); csv.printRecord("Recall (macro avg.)", round(recall.orElse(0))); - csv.printRecord("F1 Score (micro avg.)", round(2 * tp / (tpfp + tpfn))); csv.printRecord("F1 Score (macro avg.)", round(f1.orElse(0))); csv.printRecord("Cohen’s Kappa", round(computeCohenKappa(confusionMatrix))); } diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index 8ecaef57fdf..d36fff4f63c 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -317,7 +317,7 @@ private void createChoiceTab(Layout layout, String[] args) { - Precision is the fraction of instances correctly classified as belonging to a specific class out of all instances the model predicted to belong to that class. - Recall is the fraction of instances in a class that the model correctly classified out of all instances in that class. - The macro-average computes the metric independently for each class and then take the average (hence treating all classes equally). - - The micro-average will aggregate the contributions of all classes to compute the average metric. + - The micro-averages of Precision, Recall and F1 score are identical to the accuracy. - Cohen's Kappa is a measure of agreement between two raters that corrects for chance agreement. 1.0 indicates perfect agreement, 0.0 or less indicates agreement by chance. """; }); @@ -326,7 +326,6 @@ private void createChoiceTab(Layout layout, String[] args) { viz.title = "Choice Evaluation"; viz.description = "Metrics for mode choice."; viz.showAllRows = true; - viz.height = 6d; viz.dataset = data.compute(TripAnalysis.class, "mode_choice_evaluation.csv", args); }); @@ -343,7 +342,7 @@ private void createChoiceTab(Layout layout, String[] args) { viz.xAxisTitle = "Predicted"; viz.yAxisTitle = "True"; viz.y = "True/Pred"; - viz.flipAxes = "True"; + viz.flipAxes = false; viz.dataset = data.compute(TripAnalysis.class, "mode_confusion_matrix.csv", args); }); diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Heatmap.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Heatmap.java index cd689d49a29..3427ed792a3 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Heatmap.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Heatmap.java @@ -43,7 +43,7 @@ public class Heatmap extends Viz { * and y axes. Can be useful if your data is stored * one way, but you want it displayed the other. */ - public String flipAxes; + public Boolean flipAxes; public Heatmap() { super("heatmap"); From 82ef9eb4f45dfc95d4374adb50fc892ba51bfa0d Mon Sep 17 00:00:00 2001 From: rakow Date: Mon, 17 Jun 2024 17:43:56 +0200 Subject: [PATCH 27/31] allow arbitrary object for ref ids --- .../application/analysis/population/TripChoiceAnalysis.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java index cb6f2a4b151..529798d09b6 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/TripChoiceAnalysis.java @@ -70,7 +70,7 @@ public TripChoiceAnalysis(Table persons, Table trips, List modeOrder) { if (n < split.length) { String trueMode = split[n]; - data.add(new Entry((String) trip.getObject(TripAnalysis.ATTR_REF_ID), + data.add(new Entry(trip.getObject(TripAnalysis.ATTR_REF_ID), person, weight, n, trip.getLong("euclidean_distance"), trueMode, predMode)); } else log.warn("Person {} trip {} does not match ref data ({})", person, n, split.length); @@ -268,7 +268,7 @@ public void writeModePredictionError(Path path) throws IOException { } } - record Entry(String refId, String person, double weight, int n, long dist, String trueMode, String predMode) { + record Entry(Object refId, String person, double weight, int n, long dist, String trueMode, String predMode) { } record Pair(String trueMode, String predMode) { From 48088a6a3f686ddaf76011bfea3b51f4c0abd48c Mon Sep 17 00:00:00 2001 From: rakow Date: Tue, 18 Jun 2024 13:43:57 +0200 Subject: [PATCH 28/31] show heatmap labels --- .../java/org/matsim/simwrapper/dashboard/TripDashboard.java | 1 + .../src/main/java/org/matsim/simwrapper/viz/Heatmap.java | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index d36fff4f63c..b41ccd31b9b 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -343,6 +343,7 @@ private void createChoiceTab(Layout layout, String[] args) { viz.yAxisTitle = "True"; viz.y = "True/Pred"; viz.flipAxes = false; + viz.showLabels = true; viz.dataset = data.compute(TripAnalysis.class, "mode_confusion_matrix.csv", args); }); diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Heatmap.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Heatmap.java index 3427ed792a3..73ac0d689fe 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Heatmap.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/viz/Heatmap.java @@ -45,6 +45,11 @@ public class Heatmap extends Viz { */ public Boolean flipAxes; + /** + * Show labels on the heatmap. + */ + public Boolean showLabels; + public Heatmap() { super("heatmap"); } From 5488b9b9478fbcf3528577d1965de9ba893f1888 Mon Sep 17 00:00:00 2001 From: rakow Date: Mon, 24 Jun 2024 11:54:48 +0200 Subject: [PATCH 29/31] comment out trip dashboard wip part --- .../org/matsim/simwrapper/dashboard/TripDashboard.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index b41ccd31b9b..a4bb6289e0d 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -397,7 +397,12 @@ private void createGroupedTab(Layout layout, String[] args) { .orientation(BarTrace.Orientation.VERTICAL) .build(), ds); - }).el(Plotly.class, (viz, data) -> { + }); + + /* + TODO: This part needs some more work in simwrapper and is not yet ready + + .el(Plotly.class, (viz, data) -> { viz.title = "Modal distance distribution"; viz.description = "by " + cat; viz.layout = tech.tablesaw.plotly.components.Layout.builder() @@ -428,6 +433,7 @@ private void createGroupedTab(Layout layout, String[] args) { .build(), ds); }); + */ } } From 37b421b5732f29236a4f092d4649104455be5b40 Mon Sep 17 00:00:00 2001 From: rakow Date: Mon, 24 Jun 2024 12:27:16 +0200 Subject: [PATCH 30/31] use correct logger --- .../java/org/matsim/simwrapper/dashboard/TripDashboard.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index a4bb6289e0d..df3c0ed9d8d 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -1,5 +1,7 @@ package org.matsim.simwrapper.dashboard; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.matsim.application.analysis.population.TripAnalysis; import org.matsim.application.options.CsvOptions; import org.matsim.core.utils.io.IOUtils; @@ -7,8 +9,6 @@ import org.matsim.simwrapper.Header; import org.matsim.simwrapper.Layout; import org.matsim.simwrapper.viz.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import tech.tablesaw.plotly.components.Axis; import tech.tablesaw.plotly.traces.BarTrace; @@ -26,7 +26,7 @@ */ public class TripDashboard implements Dashboard { - private static final Logger log = LoggerFactory.getLogger(TripDashboard.class); + private static final Logger log = LogManager.getLogger(TripDashboard.class); @Nullable private final String modeShareRefCsv; From ebb7d683bd106285668c0510d6bdbca416e6e7ca Mon Sep 17 00:00:00 2001 From: rakow Date: Mon, 24 Jun 2024 12:44:19 +0200 Subject: [PATCH 31/31] improve wording --- .../org/matsim/application/analysis/population/Category.java | 2 +- .../java/org/matsim/simwrapper/dashboard/TripDashboard.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/population/Category.java b/contribs/application/src/main/java/org/matsim/application/analysis/population/Category.java index 3165b46d9ee..67194e881b4 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/population/Category.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/population/Category.java @@ -7,7 +7,7 @@ import java.util.regex.Pattern; /** - * Categorize values into groups. + * Helper class to categorize values into groups. */ public final class Category { diff --git a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java index df3c0ed9d8d..7ca920bfa12 100644 --- a/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java +++ b/contribs/simwrapper/src/main/java/org/matsim/simwrapper/dashboard/TripDashboard.java @@ -331,7 +331,7 @@ private void createChoiceTab(Layout layout, String[] args) { layout.row("choice", "Mode Choice").el(Table.class, (viz, data) -> { viz.title = "Choice Evaluation per Mode"; - viz.description = "Metrics for mode choice per mode."; + viz.description = "Metrics for choices per mode."; viz.showAllRows = true; viz.dataset = data.compute(TripAnalysis.class, "mode_choice_evaluation_per_mode.csv", args); });