function clusterJourneys ({
  journeys,
  log,
  patterns,
  stops
}: {
  journeys: Array<Journey>,
  log: Function,
  patterns: Array<Pattern>,
  stops: Array<Stop>
}) {
  // perform hierarchical clustering on journeys
  // see e.g. James et al., _An Introduction to Statistical Learning, with Applications in R_. New York: Springer, 2013, pg. 395.
  // convert to arrays
  const clusters = journeys.map((j) => [j])
  const inputSize = journeys.length

  // prevent infinite loop, makes sense only to loop until there's just one cluster left
  while (clusters.length > 1) {
    // find the minimum dissimilarity
    let minDis = Infinity
    let minI = 0
    let minJ = 0

    for (let i = 1; i < clusters.length; i++) {
      for (let j = 0; j < i; j++) {
        const d = getClusterDissimilarity(clusters[i], clusters[j], {patterns, stops})

        if (d < minDis) {
          minDis = d
          minI = i
          minJ = j
        }
      }
    }

    log(`dissimilarity ${minDis}`)
    if (minDis > MAX_DISSIMILARITY) break

    // cluster the least dissimilar clusters
    clusters[minI] = clusters[minI].concat(clusters[minJ])
    clusters.splice(minJ, 1) // remove clusters[j]
  }

  log(`replaced ${inputSize} journeys with ${clusters.length} clusters`)
  // merge journeys together
  return clusters.map((c) => {
    return c.reduce((j1, j2) => {
      for (let i = 0; i < j1.segments.length; i++) {
        if (j1.segments[i].type !== 'TRANSIT') continue

        // convert to pattern groups
        if (!j1.segments[i].patterns) {
          j1.segments[i].patterns = [{...j1.segments[i]}]
          j1.segments[i].pattern_id =
            j1.segments[i].from_stop_index =
            j1.segments[i].to_stop_index =
            undefined
        }

        // don't modify from and to indices, Transitive will use the stops from the first pattern
        // TODO replace with "places" (e.g. "Farragut Square Area")
        j1.segments[i].patterns.push({...j2.segments[i]})
      }

      return j1
    })
  })
}
function clusterJourneys ({
  journeys,
  log,
  patterns,
  stops
}: {
  journeys: Array<Journey>,
  log: Function,
  patterns: Array<Pattern>,
  stops: Array<Stop>
}) {
  
  
  
  const clusters = journeys.map((j) => [j])
  const inputSize = journeys.length

  
  while (clusters.length > 1) {
    
    let minDis = Infinity
    let minI = 0
    let minJ = 0

    for (let i = 1; i < clusters.length; i++) {
      for (let j = 0; j < i; j++) {
        const d = getClusterDissimilarity(clusters[i], clusters[j], {patterns, stops})

        if (d < minDis) {
          minDis = d
          minI = i
          minJ = j
        }
      }
    }

    log(`dissimilarity ${minDis}`)
    if (minDis > MAX_DISSIMILARITY) break

    
    clusters[minI] = clusters[minI].concat(clusters[minJ])
    clusters.splice(minJ, 1) 
  }

  log(`replaced ${inputSize} journeys with ${clusters.length} clusters`)
  
  return clusters.map((c) => {
    return c.reduce((j1, j2) => {
      for (let i = 0; i < j1.segments.length; i++) {
        if (j1.segments[i].type !== 'TRANSIT') continue

        
        if (!j1.segments[i].patterns) {
          j1.segments[i].patterns = [{...j1.segments[i]}]
          j1.segments[i].pattern_id =
            j1.segments[i].from_stop_index =
            j1.segments[i].to_stop_index =
            undefined
        }

        
        
        j1.segments[i].patterns.push({...j2.segments[i]})
      }

      return j1
    })
  })
}
function clusterJourneys ( { journeys , log , patterns , stops } : { journeys : Array < Journey > , log : Function , patterns : Array < Pattern > , stops : Array < Stop > } ) { // perform hierarchical clustering on journeys // see e.g. James et al., _An Introduction to Statistical Learning, with Applications in R_. New York: Springer, 2013, pg. 395. // convert to arrays const clusters = journeys . map ( ( j ) => [ j ] ) const inputSize = journeys . length // prevent infinite loop, makes sense only to loop until there's just one cluster left while ( clusters . length > 1 ) { // find the minimum dissimilarity let minDis = Infinity let minI = 0 let minJ = 0 for ( let i = 1 ; i < clusters . length ; i ++ ) { for ( let j = 0 ; j < i ; j ++ ) { const d = getClusterDissimilarity ( clusters [ i ] , clusters [ j ] , { patterns , stops } ) if ( d < minDis ) { minDis = d minI = i minJ = j } } } log ( ` ${ minDis } ` ) if ( minDis > MAX_DISSIMILARITY ) break // cluster the least dissimilar clusters clusters [ minI ] = clusters [ minI ] . concat ( clusters [ minJ ] ) clusters . splice ( minJ , 1 ) // remove clusters[j] } log ( ` ${ inputSize } ${ clusters . length } ` ) // merge journeys together return clusters . map ( ( c ) => { return c . reduce ( ( j1 , j2 ) => { for ( let i = 0 ; i < j1 . segments . length ; i ++ ) { if ( j1 . segments [ i ] . type !== 'TRANSIT' ) continue // convert to pattern groups if ( ! j1 . segments [ i ] . patterns ) { j1 . segments [ i ] . patterns = [ { ... j1 . segments [ i ] } ] j1 . segments [ i ] . pattern_id = j1 . segments [ i ] . from_stop_index = j1 . segments [ i ] . to_stop_index = undefined } // don't modify from and to indices, Transitive will use the stops from the first pattern // TODO replace with "places" (e.g. "Farragut Square Area") j1 . segments [ i ] . patterns . push ( { ... j2 . segments [ i ] } ) } return j1 } ) } ) }
Bundle similar journeys in transitive data together. Works by computing a
score for each segment based on where the endpoints are relative to each
other. It might also make sense to use a metric based on speed so that a very
slow bus isn't bundled with a fast train, but we don't currently do this.
Bundle similar journeys in transitive data together . Works by computing a score for each segment based on where the endpoints are relative to each other . It might also make sense to use a metric based on speed so that a very slow bus isn t bundled with a fast train but we don t currently do this .