Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trip sankey diagram #3504

Merged
merged 4 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -440,8 +440,13 @@ public static Path matchInput(String name, Path dir) {
if (path.isPresent())
return path.get();

// Match more general pattern at last
path = matchPattern(".+\\.[a-zA-Z0-9]*_" + name + "\\..+", dir);
// Match more general pattern
path = matchPattern(".+\\.[a-zA-Z0-9\\-]*_" + name + "\\..+", dir);
if (path.isPresent())
return path.get();

// Even more permissive pattern
path = matchPattern(".+\\.[a-zA-Z0-9_.\\-]*(_|\\.)" + name + "\\..+", dir);
if (path.isPresent())
return path.get();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.matsim.application.ApplicationUtils;
import org.matsim.application.CommandSpec;
import org.matsim.application.MATSimAppCommand;
import org.matsim.application.options.CsvOptions;
Expand All @@ -34,6 +35,7 @@
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.IntStream;

Expand All @@ -45,15 +47,13 @@
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_distance_distribution.csv",
"mode_share_distance_distribution.csv", "mode_shift.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 {

private static final Logger log = LogManager.getLogger(TripAnalysis.class);

/**
* Attributes which relates this person to a reference person.
*/
Expand All @@ -66,31 +66,24 @@ public class TripAnalysis implements MATSimAppCommand {
* Person attribute containing its weight for analysis purposes.
*/
public static final String ATTR_REF_WEIGHT = "ref_weight";

private static final Logger log = LogManager.getLogger(TripAnalysis.class);
@CommandLine.Option(names = "--person-filter", description = "Define which persons should be included into trip analysis. Map like: Attribute name (key), attribute value (value). " +
"The attribute needs to be contained by output_persons.csv. Persons who do not match all filters are filtered out.", split = ",")
private final Map<String, String> personFilters = new HashMap<>();
@CommandLine.Mixin
private InputOptions input = InputOptions.ofCommand(TripAnalysis.class);
@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;

@CommandLine.Option(names = "--dist-groups", split = ",", description = "List of distances for binning", defaultValue = "0,1000,2000,5000,10000,20000")
private List<Long> distGroups;

@CommandLine.Option(names = "--modes", split = ",", description = "List of considered modes, if not set all will be used")
private List<String> modeOrder;

@CommandLine.Option(names = "--shp-filter", description = "Define how the shp file filtering should work", defaultValue = "home")
private LocationFilter filter;

@CommandLine.Option(names = "--person-filter", description = "Define which persons should be included into trip analysis. Map like: Attribute name (key), attribute value (value). " +
"The attribute needs to be contained by output_persons.csv. Persons who do not match all filters are filtered out.", split = ",")
private final Map<String, String> personFilters = new HashMap<>();

@CommandLine.Mixin
private ShpOptions shp;

Expand Down Expand Up @@ -137,6 +130,20 @@ private static double[] calcHistogram(double[] data, double[] bins) {
return hist;
}

private static Map<String, ColumnType> getColumnTypes() {
Map<String, ColumnType> columnTypes = new HashMap<>(Map.of("person", ColumnType.TEXT,
"trav_time", ColumnType.STRING, "wait_time", ColumnType.STRING, "dep_time", ColumnType.STRING,
"longest_distance_mode", ColumnType.STRING, "main_mode", ColumnType.STRING,
"start_activity_type", ColumnType.TEXT, "end_activity_type", ColumnType.TEXT,
"first_pt_boarding_stop", ColumnType.TEXT, "last_pt_egress_stop", ColumnType.TEXT));

// Map.of only has 10 argument max
columnTypes.put("traveled_distance", ColumnType.LONG);
columnTypes.put("euclidean_distance", ColumnType.LONG);

return columnTypes;
}

@Override
public Integer call() throws Exception {

Expand Down Expand Up @@ -209,18 +216,8 @@ public Integer call() throws Exception {

log.info("Filtered {} out of {} persons", persons.rowCount(), total);

Map<String, ColumnType> columnTypes = new HashMap<>(Map.of("person", ColumnType.TEXT,
"trav_time", ColumnType.STRING, "wait_time", ColumnType.STRING, "dep_time", ColumnType.STRING,
"longest_distance_mode", ColumnType.STRING, "main_mode", ColumnType.STRING,
"start_activity_type", ColumnType.TEXT, "end_activity_type", ColumnType.TEXT,
"first_pt_boarding_stop", ColumnType.TEXT, "last_pt_egress_stop", ColumnType.TEXT));

// 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)
.columnTypesPartial(getColumnTypes())
.sample(false)
.separator(CsvOptions.detectDelimiter(input.getPath("trips.csv"))).build());

Expand Down Expand Up @@ -314,6 +311,8 @@ public Integer call() throws Exception {

writeTripDistribution(joined);

writeModeShift(joined);

return 0;
}

Expand Down Expand Up @@ -583,6 +582,34 @@ private void writeTripDistribution(Table trips) throws IOException {
}
}

private void writeModeShift(Table trips) throws IOException {
Path path;
try {
Path dir = Path.of(input.getPath("trips.csv")).getParent().resolve("ITERS").resolve("it.0");
path = ApplicationUtils.matchInput("trips.csv", dir);
} catch (Exception e) {
log.error("Could not find trips from 0th iteration.", e);
return;
}

Table originalTrips = Table.read().csv(CsvReadOptions.builder(IOUtils.getBufferedReader(path.toString()))
.columnTypesPartial(getColumnTypes())
.sample(false)
.separator(CsvOptions.detectDelimiter(path.toString())).build());

// Use longest_distance_mode where main_mode is not present
originalTrips.stringColumn("main_mode")
.set(originalTrips.stringColumn("main_mode").isMissing(),
originalTrips.stringColumn("longest_distance_mode"));

originalTrips.column("main_mode").setName("original_mode");

Table joined = new DataFrameJoiner(trips, "trip_id").inner(true, originalTrips);
Table aggr = joined.summarize("trip_id", count).by("original_mode", "main_mode");

aggr.write().csv(output.getPath("mode_shift.csv").toFile());
}

/**
* How shape file filtering should be applied.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ public void configure(Header header, Layout layout) {
.el(Plotly.class, (viz, data) -> {

viz.title = "Mode usage";
viz.description = "Share of persons using a main mode at least once per day.";
viz.description = "Share of persons using a main mode at least once per day";
viz.width = 2d;

Plotly.DataSet ds = viz.addDataset(data.compute(TripAnalysis.class, "mode_users.csv"));
Expand All @@ -267,6 +267,11 @@ public void configure(Header header, Layout layout) {
viz.mergeDatasets = true;
}

}).el(Sankey.class, (viz, data) -> {
viz.title = "Mode shift";
viz.width = 1.5d;
viz.description = "by main mode. Compares initial input with output after the last iteration";
viz.csv = data.compute(TripAnalysis.class, "mode_shift.csv", args);
});

createDistancePlot(layout, args, tab);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ void trip() {
run(new TripDashboard());
Assertions.assertThat(out)
.isDirectoryContaining("glob:**trip_stats.csv")
.isDirectoryContaining("glob:**mode_share.csv");
.isDirectoryContaining("glob:**mode_share.csv")
.isDirectoryContaining("glob:**mode_shift.csv");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,17 @@ private boolean isWriteGraph(IterationEndsEvent event){
return config.controller().getCreateGraphsInterval() > 0 && event.getIteration() % config.controller().getCreateGraphsInterval() == 0;
}

private boolean isWriteTripsAndLegs(IterationEndsEvent event){
if (config.controller().getWriteTripsInterval() <= 0) {
return false;
}
private boolean isWriteTripsAndLegs(IterationEndsEvent event) {

if (event.getIteration() == 0 || event.isLastIteration()){
return true;
// This uses the same logic as in PlansDumpingImpl
int writeTripsInterval = config.controller().getWriteTripsInterval();
final boolean writingTripsAtAll = writeTripsInterval > 0;
final boolean earlyIteration = event.getIteration() <= config.controller().getWritePlansInterval() ;

if (!writingTripsAtAll) {
return false;
}

return event.getIteration() % config.controller().getWriteTripsInterval() == 0;
return earlyIteration || event.isLastIteration() || event.getIteration() % writeTripsInterval == 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,19 @@ private static boolean checkControlerConfigGroup( Config config, Level lvl, bool
case SpeedyALT:
break;
}

if ( config.controller().getWritePlansInterval() <= 0 ) {
problem = true ;
System.out.flush() ;
log.log( lvl, "found writePlansInterval==0. vsp default is to write plans at least once (for simwrapper).") ;
}

if ( config.controller().getWriteTripsInterval() <= 0 ) {
problem = true ;
System.out.flush() ;
log.log( lvl, "found writeTripsInterval==0. vsp default is to write trips at least once (for simwrapper).") ;
}

return problem;
}

Expand Down
Loading