4
4
import it .unimi .dsi .fastutil .ints .IntList ;
5
5
import it .unimi .dsi .fastutil .ints .IntOpenHashSet ;
6
6
import it .unimi .dsi .fastutil .ints .IntSet ;
7
- import it .unimi .dsi .fastutil .objects .Object2IntLinkedOpenHashMap ;
8
- import it .unimi .dsi .fastutil .objects .Object2IntMap ;
9
- import it .unimi .dsi .fastutil .objects .Object2LongMap ;
10
- import it .unimi .dsi .fastutil .objects .Object2LongOpenHashMap ;
7
+ import it .unimi .dsi .fastutil .objects .*;
11
8
import org .apache .commons .csv .CSVFormat ;
12
9
import org .apache .commons .csv .CSVPrinter ;
13
10
import org .apache .commons .math3 .analysis .interpolation .LoessInterpolator ;
27
24
import org .matsim .core .utils .io .IOUtils ;
28
25
import picocli .CommandLine ;
29
26
import tech .tablesaw .api .*;
27
+ import tech .tablesaw .columns .strings .AbstractStringColumn ;
30
28
import tech .tablesaw .io .csv .CsvReadOptions ;
31
29
import tech .tablesaw .joining .DataFrameJoiner ;
32
30
import tech .tablesaw .selection .Selection ;
37
35
import java .nio .file .Files ;
38
36
import java .nio .file .Path ;
39
37
import java .util .*;
38
+ import java .util .stream .Collectors ;
40
39
import java .util .stream .IntStream ;
41
40
42
41
import static tech .tablesaw .aggregate .AggregateFunctions .count ;
46
45
requires = {"trips.csv" , "persons.csv" },
47
46
produces = {
48
47
"mode_share.csv" , "mode_share_per_dist.csv" , "mode_users.csv" , "trip_stats.csv" ,
49
- "mode_share_per_%s.csv" , "population_trip_stats.csv" , "trip_purposes_by_hour.csv" ,
50
- "mode_share_distance_distribution.csv" , "mode_shift.csv" ,
48
+ "mode_share_per_purpose.csv" , "mode_share_per_%s.csv" ,
49
+ "population_trip_stats.csv" , "trip_purposes_by_hour.csv" ,
50
+ "mode_share_distance_distribution.csv" , "mode_shift.csv" , "mode_chains.csv" ,
51
51
"mode_choices.csv" , "mode_choice_evaluation.csv" , "mode_choice_evaluation_per_mode.csv" ,
52
52
"mode_confusion_matrix.csv" , "mode_prediction_error.csv"
53
53
}
@@ -283,10 +283,15 @@ public Integer call() throws Exception {
283
283
284
284
joined .addColumns (dist_group );
285
285
286
+ TextColumn purpose = joined .textColumn ("end_activity_type" );
287
+
288
+ // Remove suffix durations like _345
289
+ purpose .set (Selection .withRange (0 , purpose .size ()), purpose .replaceAll ("_[0-9]{2,}$" , "" ));
290
+
286
291
writeModeShare (joined , labels );
287
292
288
293
if (groups != null ) {
289
- groups .analyzeModeShare (joined , labels , modeOrder , (g ) -> output .getPath ("mode_share_per_%s.csv" , g ));
294
+ groups .writeModeShare (joined , labels , modeOrder , (g ) -> output .getPath ("mode_share_per_%s.csv" , g ));
290
295
}
291
296
292
297
if (persons .containsColumn (ATTR_REF_MODES )) {
@@ -305,17 +310,24 @@ public Integer call() throws Exception {
305
310
306
311
writePopulationStats (persons , joined );
307
312
308
- writeTripStats (joined );
309
-
310
- writeTripPurposes (joined );
311
-
312
- writeTripDistribution (joined );
313
-
314
- writeModeShift (joined );
313
+ tryRun (this ::writeTripStats , joined );
314
+ tryRun (this ::writeTripPurposes , joined );
315
+ tryRun (this ::writeTripDistribution , joined );
316
+ tryRun (this ::writeModeShift , joined );
317
+ tryRun (this ::writeModeChains , joined );
318
+ tryRun (this ::writeModeStatsPerPurpose , joined );
315
319
316
320
return 0 ;
317
321
}
318
322
323
+ private void tryRun (ThrowingConsumer <Table > f , Table df ) {
324
+ try {
325
+ f .accept (df );
326
+ } catch (IOException e ) {
327
+ log .error ("Error while running method" , e );
328
+ }
329
+ }
330
+
319
331
private void writeModeShare (Table trips , List <String > labels ) {
320
332
321
333
Table aggr = trips .summarize ("trip_id" , count ).by ("dist_group" , "main_mode" );
@@ -502,11 +514,6 @@ private void writeTripPurposes(Table trips) {
502
514
IntColumn .create ("arrival_h" , arrival .intStream ().toArray ())
503
515
);
504
516
505
- TextColumn purpose = trips .textColumn ("end_activity_type" );
506
-
507
- // Remove suffix durations like _345
508
- purpose .set (Selection .withRange (0 , purpose .size ()), purpose .replaceAll ("_[0-9]{2,}$" , "" ));
509
-
510
517
Table tArrival = trips .summarize ("trip_id" , count ).by ("end_activity_type" , "arrival_h" );
511
518
512
519
tArrival .column (0 ).setName ("purpose" );
@@ -610,6 +617,89 @@ private void writeModeShift(Table trips) throws IOException {
610
617
aggr .write ().csv (output .getPath ("mode_shift.csv" ).toFile ());
611
618
}
612
619
620
+ /**
621
+ * Collects information about all modes used during one day.
622
+ */
623
+ private void writeModeChains (Table trips ) throws IOException {
624
+
625
+ Map <String , List <String >> modesPerPerson = new LinkedHashMap <>();
626
+
627
+ for (Row trip : trips ) {
628
+ String id = trip .getString ("person" );
629
+ String mode = trip .getString ("main_mode" );
630
+ modesPerPerson .computeIfAbsent (id , s -> new LinkedList <>()).add (mode );
631
+ }
632
+
633
+ // Store other values explicitly
634
+ ObjectDoubleMutablePair <String > other = ObjectDoubleMutablePair .of ("other" , 0 );
635
+ Object2DoubleMap <String > chains = new Object2DoubleOpenHashMap <>();
636
+ for (List <String > modes : modesPerPerson .values ()) {
637
+ String key ;
638
+ if (modes .size () == 1 )
639
+ key = modes .getFirst ();
640
+ else if (modes .size () > 6 ) {
641
+ other .right (other .rightDouble () + 1 );
642
+ continue ;
643
+ } else
644
+ key = String .join ("-" , modes );
645
+
646
+ chains .mergeDouble (key , 1 , Double ::sum );
647
+ }
648
+
649
+
650
+ List <ObjectDoubleMutablePair <String >> counts = chains .object2DoubleEntrySet ().stream ()
651
+ .map (e -> ObjectDoubleMutablePair .of (e .getKey (), (int ) e .getDoubleValue ()))
652
+ .sorted (Comparator .comparingDouble (p -> -p .rightDouble ()))
653
+ .collect (Collectors .toList ());
654
+
655
+ // Aggregate entries to prevent file from getting too large
656
+ for (int i = 250 ; i < counts .size (); i ++) {
657
+ other .right (other .rightDouble () + counts .get (i ).rightDouble ());
658
+ }
659
+ counts = counts .subList (0 , Math .min (counts .size (), 250 ));
660
+ counts .add (other );
661
+
662
+ counts .sort (Comparator .comparingDouble (p -> -p .rightDouble ()));
663
+
664
+
665
+ try (CSVPrinter printer = new CSVPrinter (Files .newBufferedWriter (output .getPath ("mode_chains.csv" )), CSVFormat .DEFAULT )) {
666
+
667
+ printer .printRecord ("modes" , "count" , "share" );
668
+
669
+ double total = counts .stream ().mapToDouble (ObjectDoubleMutablePair ::rightDouble ).sum ();
670
+ for (ObjectDoubleMutablePair <String > p : counts ) {
671
+ printer .printRecord (p .left (), (int ) p .rightDouble (), p .rightDouble () / total );
672
+ }
673
+ }
674
+ }
675
+
676
+ @ SuppressWarnings ("unchecked" )
677
+ private void writeModeStatsPerPurpose (Table trips ) {
678
+
679
+ Table aggr = trips .summarize ("trip_id" , count ).by ("end_activity_type" , "main_mode" );
680
+
681
+ Comparator <Row > cmp = Comparator .comparing (row -> row .getString ("end_activity_type" ));
682
+ aggr = aggr .sortOn (cmp .thenComparing (row -> row .getString ("main_mode" )));
683
+
684
+ aggr .doubleColumn (aggr .columnCount () - 1 ).setName ("share" );
685
+ aggr .column ("end_activity_type" ).setName ("purpose" );
686
+
687
+ Set <String > purposes = (Set <String >) aggr .column ("purpose" ).asSet ();
688
+
689
+ // Norm each purpose to 1
690
+ // It was not clear if the purpose is a string or text colum, therefor this code uses the abstract version
691
+ for (String label : purposes ) {
692
+ DoubleColumn all = aggr .doubleColumn ("share" );
693
+ Selection sel = ((AbstractStringColumn <?>) aggr .column ("purpose" )).isEqualTo (label );
694
+
695
+ double total = all .where (sel ).sum ();
696
+ if (total > 0 )
697
+ all .set (sel , all .divide (total ));
698
+ }
699
+
700
+ aggr .write ().csv (output .getPath ("mode_share_per_purpose.csv" ).toFile ());
701
+ }
702
+
613
703
/**
614
704
* How shape file filtering should be applied.
615
705
*/
@@ -619,4 +709,9 @@ enum LocationFilter {
619
709
home ,
620
710
none
621
711
}
712
+
713
+ @ FunctionalInterface
714
+ private interface ThrowingConsumer <T > {
715
+ void accept (T t ) throws IOException ;
716
+ }
622
717
}
0 commit comments