<commit_before>lightning = window.plot

createVector = lightning.createVector
createFactor = lightning.createFactor
createList = lightning.createList
createDataframe = lightning.createFrame

_assistance =
  importFiles:
    description: 'Import file(s) into H<sub>2</sub>O'
    icon: 'files-o'
  getFrames:
    description: 'Get a list of frames in H<sub>2</sub>O'
    icon: 'database'
  getModels:
    description: 'Get a list of models in H<sub>2</sub>O'
    icon: 'cubes'
  getPredictions: 
    description: 'Get a list of predictions in H<sub>2</sub>O'
    icon: 'bolt'
  getJobs:
    description: 'Get a list of jobs running in H<sub>2</sub>O'
    icon: 'tasks'
  buildModel:
    description: 'Build a model'
    icon: 'cube'
  predict:
    description: 'Make a prediction'
    icon: 'bolt'


parseInts = (source) ->
  for str in source
    if isNaN value = parseInt str, 10
      undefined
    else
      value

parseFloats = (source) ->
  for str in source
    if isNaN value = parseFloat str
      undefined
    else
      value

convertColumnToVector = (column, data) ->
  switch column.type
    when 'byte', 'short', 'int', 'long'
      createVector column.name, TNumber, parseInts data
    when 'float', 'double'
      createVector column.name, TNumber, parseFloats data
    when 'string'
      createFactor column.name, TString, data
    else
      createList column.name, data

convertTableToFrame = (table, metadata) ->
  #TODO handle format strings and description
  vectors = for column, i in table.columns
    convertColumnToVector column, table.data[i]
  createDataframe table.name, vectors, (sequence table.rowcount), null, metadata

combineTables = (tables) ->
  leader = head tables

  rowCount = 0
  columnCount = leader.data.length
  data = new Array columnCount

  for table in tables
    rowCount += table.rowcount

  for i in [0 ... columnCount]
    data[i] = columnData = new Array rowCount
    index = 0
    for table in tables
      for element in table.data[i]
        columnData[index++] = element

  name: leader.name
  columns: leader.columns
  data: data
  rowcount: rowCount

createArrays = (count, length) ->
  for i in [0 ... count]
    new Array length

parseNaNs = (source) ->
  target = new Array source.length
  for element, i in source
    target[i] = if element is 'NaN' then undefined else element
  target

parseNulls = (source) ->
  target = new Array source.length
  for element, i in source
    target[i] = if element? then element else undefined
  target

repeatValues = (count, value) ->
  target = new Array count
  for i in [0 ... count]
    target[i] = value
  target

concatArrays = (arrays) ->
  switch arrays.length
    when 0
      []
    when 1
      head arrays
    else
      a = head arrays
      a.concat.apply a, tail arrays

computeTruePositiveRate = (cm) ->
  [[tn, fp], [fn, tp]] = cm
  tp / (tp + fn)
  
computeFalsePositiveRate = (cm) ->
  [[tn, fp], [fn, tp]] = cm
  fp / (fp + tn)

formatConfusionMatrix = (cm) ->
  [[tn, fp], [fn, tp]] = cm
  [ table, tbody, tr, td ] = Flow.HTML.template 'table.flow-matrix', 'tbody', 'tr', '=td'

  table [ 
    tbody [
      tr [
        td tn
        td fp
      ]
      tr [
        td fn
        td tp
      ]
    ]
  ]

formulateGetPredictionsOrigin = (opts) ->
  if isArray opts
    sanitizedOpts = for opt in opts
      sanitizedOpt = {}
      sanitizedOpt.model = opt.model if opt.model
      sanitizedOpt.frame = opt.frame if opt.frame
      sanitizedOpt
    "getPredictions #{stringify sanitizedOpts}"
  else
    { model: modelKey, frame: frameKey } = opts
    if modelKey and frameKey
      "getPredictions model: #{stringify modelKey}, frame: #{stringify frameKey}"
    else if modelKey
      "getPredictions model: #{stringify modelKey}"
    else if frameKey
      "getPredictions frame: #{stringify frameKey}"
    else
      "getPredictions()"

H2O.Routines = (_) ->
  #TODO move these into Flow.Async
  _fork = (f, args...) -> Flow.Async.fork f, args
  _join = (args..., go) -> Flow.Async.join args, Flow.Async.applicate go
  _call = (go, args...) -> Flow.Async.join args, Flow.Async.applicate go
  _apply = (go, args) -> Flow.Async.join args, go
  _isFuture = Flow.Async.isFuture
  _async = Flow.Async.async
  _get = Flow.Async.get

  proceed = (func, args, go) ->
    go null, render_ {}, -> apply func, null, [_].concat args or []

  renderable = Flow.Async.renderable #XXX obsolete

  form = (controls, go) ->
    go null, signals controls or []

  gui = (controls) ->
    Flow.Async.renderable form, controls, (form, go) ->
      go null, Flow.Form _, form

  gui[name] = f for name, f of Flow.Gui

  flow_ = (raw) ->
    raw._flow_ or raw._flow_ = _cache_: {}

  render_ = (raw, render) ->
    (flow_ raw).render = render
    raw

  inspect_ = (raw, inspectors) ->
    root = flow_ raw
    root.inspect = {} unless root.inspect?
    for attr, f of inspectors
      root.inspect[attr] = f
    raw

  inspect = (a, b) ->
    if arguments.length is 1
      inspect$1 a
    else
      inspect$2 a, b

  inspect$1 = (obj) ->
    if _isFuture obj
      _async inspect, obj
    else
      if inspectors = obj?._flow_?.inspect
        inspections = []
        for attr, f of inspectors
          inspections.push inspect$2 attr, obj
        render_ inspections, -> H2O.InspectsOutput _, inspections
        inspections
      else
        {}

  inspect$2 = (attr, obj) ->
    return unless attr
    return _async inspect, attr, obj if _isFuture obj
    return unless obj
    return unless root = obj._flow_
    return unless inspectors = root.inspect
    return cached if cached = root._cache_[ key = "inspect_#{attr}" ]
    return unless f = inspectors[attr]
    return unless isFunction f
    root._cache_[key] = inspection = f()
    render_ inspection, -> H2O.InspectOutput _, inspection
    inspection

  _plot = (plot, go) ->
    plot (error, vis) ->
      if error
        go new Flow.Error 'Error rendering vis.', error
      else
        go null, vis

  plot = (f) ->
    if _isFuture f
      _fork proceed, H2O.PlotInput, f
    else
      renderable _plot, (f lightning), (plot, go) ->
        go null, H2O.PlotOutput _, plot.element

  grid = (f) ->
    plot (g) -> g(
      g.table()
      g.from f
    )

  extendCloud = (cloud) ->
    render_ cloud, -> H2O.CloudOutput _, cloud

  extendTimeline = (timeline) ->
    render_ timeline, -> H2O.TimelineOutput _, timeline

  extendStackTrace = (stackTrace) ->
    render_ stackTrace, -> H2O.StackTraceOutput _, stackTrace

  extendLogFile = (nodeIndex, logFile) ->
    render_ logFile, -> H2O.LogFileOutput _, nodeIndex, logFile

  extendProfile = (profile) ->
    render_ profile, -> H2O.ProfileOutput _, profile

  extendFrames = (frames) ->
    render_ frames, -> H2O.FramesOutput _, frames
    frames

  #TODO rename
  inspectMultimodelParameters = (models) -> ->
    leader = head models
    vectors = for i in [ 0 ... leader.parameters.length ]
      data = for model in models
        value = model.parameters[i].actual_value
        if value? then value else undefined
      switch parameter.type
        when 'enum', 'Frame', 'string', 'string[]', 'byte[]', 'short[]', 'int[]', 'long[]', 'float[]', 'double[]'
          createFactor parameter.label, TString, data
        when 'byte', 'short', 'int', 'long', 'float', 'double'
          createVector parameter.label, TNumber, data
        when 'boolean'
          createList parameter.label, data, (a) -> if a then 'true' else 'false'
        else
          createList parameter.label, data

    modelKeys = (model.key for model in models)

    createDataframe 'parameters', vectors, (sequence models.length), null,
      description: "Parameters for models #{modelKeys.join ', '}"
      origin: "getModels #{stringify modelKeys}"


  inspectModelParameters = (model) -> ->
    parameters = model.parameters

    attrs = [
      [ 'label', TString ]
      [ 'type', TString ]
      [ 'level', TString ]
      [ 'actual_value', TObject ]
      [ 'default_value', TObject ]
    ]

    vectors = for [ name, type ] in attrs
      data = new Array parameters.length

      for parameter, i in parameters
        data[i] = parameter[name]

      switch type
        when TString
          createFactor name, type, data
        when TObject
          createList name, data, stringify

    createDataframe 'parameters', vectors, (sequence parameters.length), null,
      description: "Parameters for model '#{model.key.name}'" #TODO frame key
      origin: "getModel #{stringify model.key.name}"

  inspectGLMModelOutput = (model) -> ->
    output = model.output

    vectors = [
      createFactor 'model_category', TString, [ output.model_category ]
      createFactor 'binomial', TString, [ output.binomial ]
      createVector 'aic', TNumber, [ output.aic ]
      createVector 'auc', TNumber, [ output.auc ]
      createVector 'best_lambda_idx', TNumber, [ output.best_lambda_idx ]
      createVector 'null_degrees_of_freedom', TNumber, [ output.null_degrees_of_freedom ]
      createVector 'null_deviance', TNumber, [ output.null_deviance ]
      createVector 'rank', TNumber, [ output.rank ]
      createVector 'residual_degrees_of_freedom', TNumber, [ output.residual_degrees_of_freedom ]
      createVector 'residual_deviance', TNumber, [ output.residual_deviance ]
      createVector 'threshold', TNumber, [ output.threshold ]
    ]

    createDataframe 'output', vectors, (sequence 1), null,
      description: "Output for GLM model '#{model.key.name}'"
      origin: "getModel #{stringify model.key.name}"

  inspectGLMCoefficientsMagnitude = (model) -> ->
    #TODO HACK
    model.output.coefficients_magnitude.columns[0].name = 'Column'
    convertTableToFrame model.output.coefficients_magnitude,
      description: "#{model.output.coefficients_magnitude.name} for GLM model #{model.key.name}"
      origin: "getModel #{stringify model.key.name}"

  inspectGLMCoefficientsTable = (model) -> ->
    #TODO HACK
    model.output.coefficients_table.columns[0].name = 'Column'
    convertTableToFrame model.output.coefficients_table,
      description: "#{model.output.coefficients_table.name} for GLM model #{model.key.name}"
      origin: "getModel #{stringify model.key.name}"

  inspectGBMModelOutput = (model) -> ->
    output = model.output

    size = output.mse_train.length

    vectors = [
      createVector 'tree', TNumber, (sequence size)
      createVector 'mse_train', TNumber, parseNaNs output.mse_train
      createVector 'mse_valid', TNumber, parseNaNs output.mse_valid
    ]

    createDataframe 'output', vectors, (sequence size), null,
      description: "Output for GBM model '#{model.key.name}'"
      origin: "getModel #{stringify model.key.name}"

  inspectKMeansModelOutput = (model) -> ->
    output = model.output

    vectors = [
      createVector 'avg_between_ss', TNumber, [ output.avg_between_ss ]
      createVector 'avg_ss', TNumber, [ output.avg_ss ]
      createVector 'avg_within_ss', TNumber, [ output.avg_within_ss ]
      createVector 'categorical_column_count', TNumber, [ output.categorical_column_count ]
      createVector 'iterations', TNumber, [ output.iterations ]
      createFactor 'model_category', TString, [ output.model_category ]

    ]

    createDataframe 'output', vectors, (sequence 1), null,
      description: "Output for k-means model '#{model.key.name}'"
      origin: "getModel #{stringify model.key.name}"

  inspectKmeansModelClusters = (model) -> ->
    output = model.output

    vectors = [
      createFactor 'cluster', TString, head output.centers.data
      createVector 'size', TNumber, output.size
      createVector 'within_mse', TNumber, output.within_mse
    ]

    createDataframe 'clusters', vectors, (sequence output.size.length), null, 
      description: "Clusters for k-means model '#{model.key.name}'"
      origin: "getModel #{stringify model.key.name}"

  inspectKmeansModelClusterMeans = (model) -> ->
    convertTableToFrame model.output.centers, 
      description: "Cluster means for k-means model '#{model.key.name}'"
      origin: "getModel #{stringify model.key.name}"

  extendKMeansModel = (model) ->
    inspect_ model,
      parameters: inspectModelParameters model
      output: inspectKMeansModelOutput model
      clusters: inspectKmeansModelClusters model
      'Cluster means': inspectKmeansModelClusterMeans model

  extendDeepLearningModel = (model) ->
    inspect_ model,
      parameters: inspectModelParameters model
  
  extendGBMModel = (model) ->
    inspect_ model,
      parameters: inspectModelParameters model
      output: inspectGBMModelOutput model

  extendGLMModel = (model) ->
    inspections = {}
    inspections.parameters = inspectModelParameters model
    inspections.output = inspectGLMModelOutput model
    inspections[model.output.coefficients_magnitude.name] = inspectGLMCoefficientsMagnitude model 
    inspections[model.output.coefficients_table.name] = inspectGLMCoefficientsTable model
    inspect_ model, inspections

  extendJob = (job) ->
    render_ job, -> H2O.JobOutput _, job

  extendModel = (model) ->
    switch model.algo
      when 'kmeans'
        extendKMeansModel model
      when 'deeplearning'
        extendDeepLearningModel model
      when 'gbm'
        extendGBMModel model
      when 'glm'
        extendGLMModel model

    render_ model, -> H2O.ModelOutput _, model

  extendModels = (models) ->
    for model in models
      extendModel model

    algos = unique (model.algo for model in models)
    if algos.length is 1
      inspect_ models,
        parameters: inspectMultimodelParameters models 


    render_ models, -> H2O.ModelsOutput _, models

  read = (value) -> if value is 'NaN' then null else value

  inspectRegressionPrediction = (prediction) -> ->
    { frame, model, predictions } = prediction

    vectors = [
      createFactor 'key', TString, [ model.name ]
      createFactor 'frame', TString, [ frame.name ]
      createFactor 'model_category', TString, [ prediction.model_category ]
      createVector 'duration_in_ms', TString, [ prediction.duration_in_ms ]
      createVector 'scoring_time', TString, [ prediction.scoring_time ]
    ]

    createDataframe 'prediction', vectors, (sequence 1), null,
      description: "Prediction output for model '#{model.name}' on frame '#{frame.name}'"
      origin: "getPrediction #{stringify model.name}, #{stringify frame.name}"

  inspectBinomialPrediction = (prediction) -> ->
    { frame, model } = prediction

    vectors = [
      createFactor 'key', TString, [ model.name ]
      createFactor 'frame', TString, [ frame.name ]
      createFactor 'model_category', TString, [ prediction.model_category ]
      createVector 'AUC', TNumber, [ prediction.AUC ]
      createVector 'Gini', TNumber, [ prediction.Gini ]
      createVector 'mse', TNumber, [ prediction.mse ]
      createVector 'duration_in_ms', TNumber, [ prediction.duration_in_ms ]
      createVector 'scoring_time', TNumber, [ prediction.scoring_time ]
    ]

    createDataframe 'Prediction', vectors, (sequence 1), null,
      description: "Prediction output for model '#{model.name}' on frame '#{frame.name}'"
      origin: "getPrediction #{stringify model.name}, #{stringify frame.name}"

  inspectBinomialConfusionMatrices = (opts, predictions) -> ->
    vectors = [
      createList 'CM', (concatArrays (prediction.confusion_matrices for prediction in predictions)), formatConfusionMatrix
      createVector 'TPR', TNumber, concatArrays (map prediction.confusion_matrices, computeTruePositiveRate for prediction in predictions)
      createVector 'FPR', TNumber, concatArrays (map prediction.confusion_matrices, computeFalsePositiveRate for prediction in predictions)
      createFactor 'key', TString, concatArrays (repeatValues prediction.confusion_matrices.length, prediction.model.name + ' on ' + prediction.frame.name for prediction in predictions)
    ]

    createDataframe 'Confusion Matrices', vectors, (sequence (head vectors).count()), null,
      description: "Confusion matrices for the selected predictions"
      origin: formulateGetPredictionsOrigin opts
      plot: "plot inspect 'Confusion Matrices', #{formulateGetPredictionsOrigin opts}"

  inspectBinomialMetrics = (opts, predictions) -> ->
    inspectionFrame = combineTables (prediction.maxCriteriaAndMetricScores for prediction in predictions)
    convertTableToFrame inspectionFrame,
      description: "Metrics for the selected predictions"
      origin: formulateGetPredictionsOrigin opts<commit_msg>Process 2d tables for DL model outputs<commit_after>lightning = window.plot

createVector = lightning.createVector
createFactor = lightning.createFactor
createList = lightning.createList
createDataframe = lightning.createFrame

_assistance =
  importFiles:
    description: 'Import file(s) into H<sub>2</sub>O'
    icon: 'files-o'
  getFrames:
    description: 'Get a list of frames in H<sub>2</sub>O'
    icon: 'database'
  getModels:
    description: 'Get a list of models in H<sub>2</sub>O'
    icon: 'cubes'
  getPredictions: 
    description: 'Get a list of predictions in H<sub>2</sub>O'
    icon: 'bolt'
  getJobs:
    description: 'Get a list of jobs running in H<sub>2</sub>O'
    icon: 'tasks'
  buildModel:
    description: 'Build a model'
    icon: 'cube'
  predict:
    description: 'Make a prediction'
    icon: 'bolt'


parseInts = (source) ->
  for str in source
    if isNaN value = parseInt str, 10
      undefined
    else
      value

parseFloats = (source) ->
  for str in source
    if isNaN value = parseFloat str
      undefined
    else
      value

convertColumnToVector = (column, data) ->
  switch column.type
    when 'byte', 'short', 'int', 'long'
      createVector column.name, TNumber, parseInts data
    when 'float', 'double'
      createVector column.name, TNumber, parseFloats data
    when 'string'
      createFactor column.name, TString, data
    else
      createList column.name, data

convertTableToFrame = (table, metadata) ->
  #TODO handle format strings and description
  vectors = for column, i in table.columns
    convertColumnToVector column, table.data[i]
  createDataframe table.name, vectors, (sequence table.rowcount), null, metadata

combineTables = (tables) ->
  leader = head tables

  rowCount = 0
  columnCount = leader.data.length
  data = new Array columnCount

  for table in tables
    rowCount += table.rowcount

  for i in [0 ... columnCount]
    data[i] = columnData = new Array rowCount
    index = 0
    for table in tables
      for element in table.data[i]
        columnData[index++] = element

  name: leader.name
  columns: leader.columns
  data: data
  rowcount: rowCount

createArrays = (count, length) ->
  for i in [0 ... count]
    new Array length

parseNaNs = (source) ->
  target = new Array source.length
  for element, i in source
    target[i] = if element is 'NaN' then undefined else element
  target

parseNulls = (source) ->
  target = new Array source.length
  for element, i in source
    target[i] = if element? then element else undefined
  target

repeatValues = (count, value) ->
  target = new Array count
  for i in [0 ... count]
    target[i] = value
  target

concatArrays = (arrays) ->
  switch arrays.length
    when 0
      []
    when 1
      head arrays
    else
      a = head arrays
      a.concat.apply a, tail arrays

computeTruePositiveRate = (cm) ->
  [[tn, fp], [fn, tp]] = cm
  tp / (tp + fn)
  
computeFalsePositiveRate = (cm) ->
  [[tn, fp], [fn, tp]] = cm
  fp / (fp + tn)

formatConfusionMatrix = (cm) ->
  [[tn, fp], [fn, tp]] = cm
  [ table, tbody, tr, td ] = Flow.HTML.template 'table.flow-matrix', 'tbody', 'tr', '=td'

  table [ 
    tbody [
      tr [
        td tn
        td fp
      ]
      tr [
        td fn
        td tp
      ]
    ]
  ]

formulateGetPredictionsOrigin = (opts) ->
  if isArray opts
    sanitizedOpts = for opt in opts
      sanitizedOpt = {}
      sanitizedOpt.model = opt.model if opt.model
      sanitizedOpt.frame = opt.frame if opt.frame
      sanitizedOpt
    "getPredictions #{stringify sanitizedOpts}"
  else
    { model: modelKey, frame: frameKey } = opts
    if modelKey and frameKey
      "getPredictions model: #{stringify modelKey}, frame: #{stringify frameKey}"
    else if modelKey
      "getPredictions model: #{stringify modelKey}"
    else if frameKey
      "getPredictions frame: #{stringify frameKey}"
    else
      "getPredictions()"

H2O.Routines = (_) ->
  #TODO move these into Flow.Async
  _fork = (f, args...) -> Flow.Async.fork f, args
  _join = (args..., go) -> Flow.Async.join args, Flow.Async.applicate go
  _call = (go, args...) -> Flow.Async.join args, Flow.Async.applicate go
  _apply = (go, args) -> Flow.Async.join args, go
  _isFuture = Flow.Async.isFuture
  _async = Flow.Async.async
  _get = Flow.Async.get

  proceed = (func, args, go) ->
    go null, render_ {}, -> apply func, null, [_].concat args or []

  renderable = Flow.Async.renderable #XXX obsolete

  form = (controls, go) ->
    go null, signals controls or []

  gui = (controls) ->
    Flow.Async.renderable form, controls, (form, go) ->
      go null, Flow.Form _, form

  gui[name] = f for name, f of Flow.Gui

  flow_ = (raw) ->
    raw._flow_ or raw._flow_ = _cache_: {}

  render_ = (raw, render) ->
    (flow_ raw).render = render
    raw

  inspect_ = (raw, inspectors) ->
    root = flow_ raw
    root.inspect = {} unless root.inspect?
    for attr, f of inspectors
      root.inspect[attr] = f
    raw

  inspect = (a, b) ->
    if arguments.length is 1
      inspect$1 a
    else
      inspect$2 a, b

  inspect$1 = (obj) ->
    if _isFuture obj
      _async inspect, obj
    else
      if inspectors = obj?._flow_?.inspect
        inspections = []
        for attr, f of inspectors
          inspections.push inspect$2 attr, obj
        render_ inspections, -> H2O.InspectsOutput _, inspections
        inspections
      else
        {}

  inspect$2 = (attr, obj) ->
    return unless attr
    return _async inspect, attr, obj if _isFuture obj
    return unless obj
    return unless root = obj._flow_
    return unless inspectors = root.inspect
    return cached if cached = root._cache_[ key = "inspect_#{attr}" ]
    return unless f = inspectors[attr]
    return unless isFunction f
    root._cache_[key] = inspection = f()
    render_ inspection, -> H2O.InspectOutput _, inspection
    inspection

  _plot = (plot, go) ->
    plot (error, vis) ->
      if error
        go new Flow.Error 'Error rendering vis.', error
      else
        go null, vis

  plot = (f) ->
    if _isFuture f
      _fork proceed, H2O.PlotInput, f
    else
      renderable _plot, (f lightning), (plot, go) ->
        go null, H2O.PlotOutput _, plot.element

  grid = (f) ->
    plot (g) -> g(
      g.table()
      g.from f
    )

  extendCloud = (cloud) ->
    render_ cloud, -> H2O.CloudOutput _, cloud

  extendTimeline = (timeline) ->
    render_ timeline, -> H2O.TimelineOutput _, timeline

  extendStackTrace = (stackTrace) ->
    render_ stackTrace, -> H2O.StackTraceOutput _, stackTrace

  extendLogFile = (nodeIndex, logFile) ->
    render_ logFile, -> H2O.LogFileOutput _, nodeIndex, logFile

  extendProfile = (profile) ->
    render_ profile, -> H2O.ProfileOutput _, profile

  extendFrames = (frames) ->
    render_ frames, -> H2O.FramesOutput _, frames
    frames

  #TODO rename
  inspectMultimodelParameters = (models) -> ->
    leader = head models
    vectors = for i in [ 0 ... leader.parameters.length ]
      data = for model in models
        value = model.parameters[i].actual_value
        if value? then value else undefined
      switch parameter.type
        when 'enum', 'Frame', 'string', 'string[]', 'byte[]', 'short[]', 'int[]', 'long[]', 'float[]', 'double[]'
          createFactor parameter.label, TString, data
        when 'byte', 'short', 'int', 'long', 'float', 'double'
          createVector parameter.label, TNumber, data
        when 'boolean'
          createList parameter.label, data, (a) -> if a then 'true' else 'false'
        else
          createList parameter.label, data

    modelKeys = (model.key for model in models)

    createDataframe 'parameters', vectors, (sequence models.length), null,
      description: "Parameters for models #{modelKeys.join ', '}"
      origin: "getModels #{stringify modelKeys}"


  inspectModelParameters = (model) -> ->
    parameters = model.parameters

    attrs = [
      [ 'label', TString ]
      [ 'type', TString ]
      [ 'level', TString ]
      [ 'actual_value', TObject ]
      [ 'default_value', TObject ]
    ]

    vectors = for [ name, type ] in attrs
      data = new Array parameters.length

      for parameter, i in parameters
        data[i] = parameter[name]

      switch type
        when TString
          createFactor name, type, data
        when TObject
          createList name, data, stringify

    createDataframe 'parameters', vectors, (sequence parameters.length), null,
      description: "Parameters for model '#{model.key.name}'" #TODO frame key
      origin: "getModel #{stringify model.key.name}"

  inspectGLMModelOutput = (model) -> ->
    output = model.output

    vectors = [
      createFactor 'model_category', TString, [ output.model_category ]
      createFactor 'binomial', TString, [ output.binomial ]
      createVector 'aic', TNumber, [ output.aic ]
      createVector 'auc', TNumber, [ output.auc ]
      createVector 'best_lambda_idx', TNumber, [ output.best_lambda_idx ]
      createVector 'null_degrees_of_freedom', TNumber, [ output.null_degrees_of_freedom ]
      createVector 'null_deviance', TNumber, [ output.null_deviance ]
      createVector 'rank', TNumber, [ output.rank ]
      createVector 'residual_degrees_of_freedom', TNumber, [ output.residual_degrees_of_freedom ]
      createVector 'residual_deviance', TNumber, [ output.residual_deviance ]
      createVector 'threshold', TNumber, [ output.threshold ]
    ]

    createDataframe 'output', vectors, (sequence 1), null,
      description: "Output for GLM model '#{model.key.name}'"
      origin: "getModel #{stringify model.key.name}"

  inspectGLMCoefficientsMagnitude = (model) -> ->
    convertTableToFrame model.output.coefficients_magnitude,
      description: "#{model.output.coefficients_magnitude.name} for GLM model #{model.key.name}"
      origin: "getModel #{stringify model.key.name}"

  inspectGLMCoefficientsTable = (model) -> ->
    convertTableToFrame model.output.coefficients_table,
      description: "#{model.output.coefficients_table.name} for GLM model #{model.key.name}"
      origin: "getModel #{stringify model.key.name}"

  inspectGBMModelOutput = (model) -> ->
    output = model.output

    size = output.mse_train.length

    vectors = [
      createVector 'tree', TNumber, (sequence size)
      createVector 'mse_train', TNumber, parseNaNs output.mse_train
      createVector 'mse_valid', TNumber, parseNaNs output.mse_valid
    ]

    createDataframe 'output', vectors, (sequence size), null,
      description: "Output for GBM model '#{model.key.name}'"
      origin: "getModel #{stringify model.key.name}"

  inspectKMeansModelOutput = (model) -> ->
    output = model.output

    vectors = [
      createVector 'avg_between_ss', TNumber, [ output.avg_between_ss ]
      createVector 'avg_ss', TNumber, [ output.avg_ss ]
      createVector 'avg_within_ss', TNumber, [ output.avg_within_ss ]
      createVector 'categorical_column_count', TNumber, [ output.categorical_column_count ]
      createVector 'iterations', TNumber, [ output.iterations ]
      createFactor 'model_category', TString, [ output.model_category ]

    ]

    createDataframe 'output', vectors, (sequence 1), null,
      description: "Output for k-means model '#{model.key.name}'"
      origin: "getModel #{stringify model.key.name}"

  inspectKmeansModelClusters = (model) -> ->
    output = model.output

    vectors = [
      createFactor 'cluster', TString, head output.centers.data
      createVector 'size', TNumber, output.size
      createVector 'within_mse', TNumber, output.within_mse
    ]

    createDataframe 'clusters', vectors, (sequence output.size.length), null, 
      description: "Clusters for k-means model '#{model.key.name}'"
      origin: "getModel #{stringify model.key.name}"

  inspectKmeansModelClusterMeans = (model) -> ->
    convertTableToFrame model.output.centers, 
      description: "Cluster means for k-means model '#{model.key.name}'"
      origin: "getModel #{stringify model.key.name}"

  extendKMeansModel = (model) ->
    inspect_ model,
      parameters: inspectModelParameters model
      output: inspectKMeansModelOutput model
      clusters: inspectKmeansModelClusters model
      'Cluster means': inspectKmeansModelClusterMeans model

  extendDeepLearningModel = (model) ->

    origin = "getModel #{stringify model.key.name}"

    inspections = {}
    inspections.parameters = inspectModelParameters model

    if model.output.model_category is 'Binomial'
      tables = [ model.output.modelSummary, model.output.scoringHistory ]
      tables.forEach (table) ->
        inspections[ table.name ] = ->
          convertTableToFrame table,
            description: table.name 
            origin: origin

      if prediction1 = model.output.trainMetrics
        prediction1.thresholdsAndMetricScores.name = 'Training ' + prediction1.thresholdsAndMetricScores.name
        prediction1.maxCriteriaAndMetricScores.name = 'Training ' + prediction1.maxCriteriaAndMetricScores.name

        inspections[ 'Training Metrics' ] = inspectBinomialPrediction2 'Training Metrics', prediction1

        inspections[ prediction1.thresholdsAndMetricScores.name ] = -> 
          convertTableToFrame prediction1.thresholdsAndMetricScores,
            description: prediction1.thresholdsAndMetricScores.name
            origin: origin
            plot: "plot inspect '#{prediction1.thresholdsAndMetricScores.name}', #{origin}"

        inspections[ prediction1.maxCriteriaAndMetricScores.name ] = -> 
          convertTableToFrame prediction1.maxCriteriaAndMetricScores,
          description: prediction1.maxCriteriaAndMetricScores.name
          origin: origin
          plot: "plot inspect '#{prediction1.maxCriteriaAndMetricScores.name}', #{origin}"

        inspections[ 'Training Confusion Matrices' ] = inspectBinomialConfusionMatrices2 'Training Confusion Matrices', prediction1

      if prediction2 = model.output.validMetrics
        prediction2.thresholdsAndMetricScores.name = 'Validation ' + prediction2.thresholdsAndMetricScores.name
        prediction2.maxCriteriaAndMetricScores.name = 'Validation ' + prediction2.maxCriteriaAndMetricScores.name

        inspections[ 'Validation Metrics' ] = inspectBinomialPrediction2 'Validation Metrics', prediction2
        inspections[ prediction2.thresholdsAndMetricScores.name ] = -> 
          convertTableToFrame prediction2.thresholdsAndMetricScores,
          description: prediction2.thresholdsAndMetricScores.name
          origin: origin
          plot: "plot inspect '#{prediction2.thresholdsAndMetricScores.name}', #{origin}"

        inspections[ prediction2.maxCriteriaAndMetricScores.name ] = -> 
        convertTableToFrame prediction2.maxCriteriaAndMetricScores,
          description: prediction2.maxCriteriaAndMetricScores.name
          origin: origin
          plot: "plot inspect '#{prediction2.maxCriteriaAndMetricScores.name}', #{origin}"

        inspections[ 'Validation Confusion Matrices' ] = inspectBinomialConfusionMatrices2 'Validation Confusion Matrices', prediction2

    inspect_ model, inspections
  
  extendGBMModel = (model) ->
    inspect_ model,
      parameters: inspectModelParameters model
      output: inspectGBMModelOutput model

  extendGLMModel = (model) ->
    inspections = {}
    inspections.parameters = inspectModelParameters model
    inspections.output = inspectGLMModelOutput model
    inspections[model.output.coefficients_magnitude.name] = inspectGLMCoefficientsMagnitude model 
    inspections[model.output.coefficients_table.name] = inspectGLMCoefficientsTable model
    inspect_ model, inspections

  extendJob = (job) ->
    render_ job, -> H2O.JobOutput _, job

  extendModel = (model) ->
    switch model.algo
      when 'kmeans'
        extendKMeansModel model
      when 'deeplearning'
        extendDeepLearningModel model
      when 'gbm'
        extendGBMModel model
      when 'glm'
        extendGLMModel model

    render_ model, -> H2O.ModelOutput _, model

  extendModels = (models) ->
    for model in models
      extendModel model

    algos = unique (model.algo for model in models)
    if algos.length is 1
      inspect_ models,
        parameters: inspectMultimodelParameters models 


    render_ models, -> H2O.ModelsOutput _, models

  read = (value) -> if value is 'NaN' then null else value

  inspectRegressionPrediction = (prediction) -> ->
    { frame, model, predictions } = prediction

    vectors = [
      createFactor 'key', TString, [ model.name ]
      createFactor 'frame', TString, [ frame.name ]
      createFactor 'model_category', TString, [ prediction.model_category ]
      createVector 'duration_in_ms', TString, [ prediction.duration_in_ms ]
      createVector 'scoring_time', TString, [ prediction.scoring_time ]
    ]

    createDataframe 'prediction', vectors, (sequence 1), null,
      description: "Prediction output for model '#{model.name}' on frame '#{frame.name}'"
      origin: "getPrediction #{stringify model.name}, #{stringify frame.name}"

  inspectBinomialPrediction2 = (frameLabel, prediction) -> ->
    origin = "getModel #{stringify prediction.model.name}"
    { frame, model } = prediction

    vectors = [
      createFactor 'key', TString, [ model.name ]
      createFactor 'frame', TString, [ frame.name ]
      createFactor 'model_category', TString, [ prediction.model_category ]
      createVector 'AUC', TNumber, [ prediction.AUC ]
      createVector 'Gini', TNumber, [ prediction.Gini ]
      createVector 'mse', TNumber, [ prediction.mse ]
      createVector 'duration_in_ms', TNumber, [ prediction.duration_in_ms ]
      createVector 'scoring_time', TNumber, [ prediction.scoring_time ]
    ]

    createDataframe frameLabel, vectors, (sequence 1), null,
      description: frameLabel
      origin: origin

  inspectBinomialPrediction = (prediction) -> ->
    { frame, model } = prediction

    vectors = [
      createFactor 'key', TString, [ model.name ]
      createFactor 'frame', TString, [ frame.name ]
      createFactor 'model_category', TString, [ prediction.model_category ]
      createVector 'AUC', TNumber, [ prediction.AUC ]
      createVector 'Gini', TNumber, [ prediction.Gini ]
      createVector 'mse', TNumber, [ prediction.mse ]
      createVector 'duration_in_ms', TNumber, [ prediction.duration_in_ms ]
      createVector 'scoring_time', TNumber, [ prediction.scoring_time ]
    ]

    createDataframe 'Prediction', vectors, (sequence 1), null,
      description: "Prediction output for model '#{model.name}' on frame '#{frame.name}'"
      origin: "getPrediction #{stringify model.name}, #{stringify frame.name}"

  inspectBinomialConfusionMatrices2 = (frameLabel, prediction) -> ->
    origin = "getModel #{stringify prediction.model.name}"
    vectors = [
      createList 'CM', prediction.confusion_matrices, formatConfusionMatrix
      createVector 'TPR', TNumber, map prediction.confusion_matrices, computeTruePositiveRate
      createVector 'FPR', TNumber, map prediction.confusion_matrices, computeFalsePositiveRate
    ]
    createDataframe frameLabel, vectors, (sequence prediction.confusion_matrices.length), null,
      description: frameLabel
      origin: origin
      plot: "plot inspect '#{frameLabel}', #{origin}"


  inspectBinomialConfusionMatrices = (opts, predictions) -> ->
    vectors = [
      createList 'CM', (concatArrays (prediction.confusion_matrices for prediction in predictions)), formatConfusionMatrix
      createVector 'TPR', TNumber, concatArrays (map prediction.confusion_matrices, computeTruePositiveRate for prediction in predictions)
      createVector 'FPR', TNumber, concatArrays (map prediction.confusion_matrices, computeFalsePositiveRate for prediction in predictions)
      createFactor 'key', TString, concatArrays (repeatValues prediction.confusion_matrices.length, prediction.model.name + ' on ' + prediction.frame.name for prediction in predictions)
    ]

    createDataframe 'Confusion Matrices', vectors, (sequence (head vectors).count()), null,
      description: "Confusion matrices for the selected predictions"
      origin: formulateGetPredictionsOrigin opts
      plot: "plot inspect 'Confusion Matrices', #{formulateGetPredictionsOrigin opts}"

  inspectBinomialMetrics = (opts, predictions) -> ->
    inspectionFrame = combineTables (prediction.maxCriteriaAndMetricScores for prediction in predictions)
    convertTableToFrame inspectionFrame,
      description: "Metrics for the selected predictions"
      origin: formulateGetPredictionsOrigin opts
{'fraction_non_alphanumeric': 0.10543381339674134, 'fraction_numerical': 0.0032424545380853307, 'mean_word_length': 3.144456886898096, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 209, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '498684', 'n_tokens_mistral': 11104, 'n_tokens_neox': 10742, 'n_words': 3008}