Class: DaruLite::MultiIndex

Inherits:
Index show all
Defined in:
lib/daru_lite/index/multi_index.rb

Overview

rubocop:disable Metrics/ClassLength

Instance Attribute Summary collapse

Attributes inherited from Index

#relation_hash

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Index

__new__, #_dump, _load, coerce, inherited, #is_values, new, #slice, #sort, #subset_slice

Constructor Details

#initialize(opts = {}) ⇒ MultiIndex

names and levels should be of same size. If size of Array ‘name` is less or greater than size of array `levels` then it raises `SizeError`. If user don’t want to put name for particular level then user must put empty string in that index of Array ‘name`. For example there is multi_index of 3 levels and user don’t want to name level 0, then do multi_index.name = [”, ‘level1_name1’, ‘level2_name’]

Examples:


# set the name during initialization

mi = DaruLite::MultiIndex.new(
    levels: [[:a,:b,:c], [:one, :two]],
    labels: [[0,0,1,1,2,2], [0,1,0,1,0,1]], name: ['s1', 's2'])

# =>
# <DaruLite::MultiIndex(6x2)>
#   s1  s2
#    a one
#      two
#    b one
#      two
#    c one
#      two

# set new name

mi.name = ['k1', 'k2']
=> ["k1", "k2"]

mi
=>
#   #<DaruLite::MultiIndex(6x2)>
#   k1  k2
#    a one
#      two
#    b one
#      two
#    c one
#      two

# access the name

mi.name
=> ["k1", "k2"]

# If you don't want to name level 0

mi.name = ['', 'k2']
=> ["", "k2"]

mi
=>
#<DaruLite::MultiIndex(6x2)>
#       k2
#    a one
#      two
#    b one
#      two
#    c one
#      two

Raises:

  • (ArgumentError)


79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/daru_lite/index/multi_index.rb', line 79

def initialize(opts = {})
  labels = opts[:labels]
  levels = opts[:levels]

  raise ArgumentError, 'Must specify both labels and levels' unless labels && levels
  raise ArgumentError, 'Labels and levels should be same size' if labels.size != levels.size
  raise ArgumentError, 'Incorrect labels and levels' if incorrect_fields?(labels, levels)

  @labels = labels
  @levels = levels.map { |e| e.map.with_index.to_h }
  self.name = opts[:name] unless opts[:name].nil?
end

Instance Attribute Details

#labelsObject (readonly)

Returns the value of attribute labels.



11
12
13
# File 'lib/daru_lite/index/multi_index.rb', line 11

def labels
  @labels
end

#nameObject

Returns the value of attribute name.



11
12
13
# File 'lib/daru_lite/index/multi_index.rb', line 11

def name
  @name
end

Class Method Details

.from_arrays(arrays) ⇒ Object



105
106
107
108
109
110
111
112
113
114
# File 'lib/daru_lite/index/multi_index.rb', line 105

def self.from_arrays(arrays)
  levels = arrays.map { |e| e.uniq.sort_by(&:to_s) }

  labels = arrays.each_with_index.map do |arry, level_index|
    level = levels[level_index]
    arry.map { |lvl| level.index(lvl) }
  end

  MultiIndex.new labels: labels, levels: levels
end

.from_tuples(tuples) ⇒ Object



116
117
118
# File 'lib/daru_lite/index/multi_index.rb', line 116

def self.from_tuples(tuples)
  from_arrays tuples.transpose
end

.try_from_tuples(tuples) ⇒ Object



120
121
122
# File 'lib/daru_lite/index/multi_index.rb', line 120

def self.try_from_tuples(tuples)
  from_tuples(tuples) if tuples.respond_to?(:first) && tuples.first.is_a?(Array)
end

Instance Method Details

#&(other) ⇒ Object



318
319
320
# File 'lib/daru_lite/index/multi_index.rb', line 318

def &(other)
  MultiIndex.from_tuples(to_a & other.to_a)
end

#==(other) ⇒ Object



342
343
344
345
346
# File 'lib/daru_lite/index/multi_index.rb', line 342

def ==(other)
  self.class == other.class  &&
    labels   == other.labels &&
    levels   == other.levels
end

#[](*key) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/daru_lite/index/multi_index.rb', line 124

def [](*key)
  if key.all? { |subkey| subkey.is_a?(Array) && subkey.length > 1 }
    retrieve_from_tuples(*key)
  else
    key.flatten!
    if key[0].is_a?(Range)
      retrieve_from_range(key[0])
    elsif key[0].is_a?(Integer) && key.size == 1
      try_retrieve_from_integer(key[0])
    else
      retrieve_from_tuple(key)
    end
  end
end

#add(*indexes) ⇒ Object



199
200
201
# File 'lib/daru_lite/index/multi_index.rb', line 199

def add(*indexes)
  DaruLite::MultiIndex.from_tuples(to_a + [indexes])
end

#at(*positions) ⇒ object

Takes positional values and returns subset of the self

capturing the indexes at mentioned positions

Examples:

idx = DaruLite::MultiIndex.from_tuples [[:a, :one], [:a, :two], [:b, :one], [:b, :two]]
idx.at 0, 1
# => #<DaruLite::MultiIndex(2x2)>
#   a one
#     two

Parameters:

  • positions (Array<Integer>)

    positional values

Returns:

  • (object)

    index object



189
190
191
192
193
194
195
196
197
# File 'lib/daru_lite/index/multi_index.rb', line 189

def at(*positions)
  positions = preprocess_positions(*positions)
  validate_positions(*positions)
  if positions.is_a? Integer
    key(positions)
  else
    DaruLite::MultiIndex.from_tuples(positions.map { |v| key(v) })
  end
end

#coerce_indexObject



269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/daru_lite/index/multi_index.rb', line 269

def coerce_index
  if @levels.size == 1
    elements = to_a.flatten

    if elements.uniq.length == elements.length
      DaruLite::Index.new(elements)
    else
      DaruLite::CategoricalIndex.new(elements)
    end
  else
    self
  end
end

#conform(input_indexes) ⇒ Object

Provide a MultiIndex for sub vector produced

Parameters:

  • input_indexes (Array)

    the input by user to index the vector

Returns:

  • (Object)

    the MultiIndex object for sub vector produced



370
371
372
373
374
# File 'lib/daru_lite/index/multi_index.rb', line 370

def conform(input_indexes)
  return self if input_indexes[0].is_a? Range

  drop_left_level input_indexes.size
end

#delete_at(position) ⇒ object

Takes a positional value and returns a new Index without the element at given position

Parameters:

  • position (Integer)

    positional value

Returns:

  • (object)

    index object



206
207
208
209
210
# File 'lib/daru_lite/index/multi_index.rb', line 206

def delete_at(position)
  indexes = to_a
  indexes.delete_at(position)
  self.class.from_tuples(indexes)
end

#drop_left_level(by = 1) ⇒ Object



310
311
312
# File 'lib/daru_lite/index/multi_index.rb', line 310

def drop_left_level(by = 1)
  MultiIndex.from_arrays to_a.transpose[by..]
end

#dupObject



306
307
308
# File 'lib/daru_lite/index/multi_index.rb', line 306

def dup
  MultiIndex.new levels: levels.dup, labels: labels.dup, name: @name&.dup
end

#eachObject



3
4
5
# File 'lib/daru_lite/index/multi_index.rb', line 3

def each(&)
  to_a.each(&)
end

#empty?Boolean

Returns:

  • (Boolean)


322
323
324
# File 'lib/daru_lite/index/multi_index.rb', line 322

def empty?
  @labels.flatten.empty? && @levels.all?(&:empty?)
end

#include?(tuple) ⇒ Boolean

Returns:

  • (Boolean)


326
327
328
329
330
331
332
# File 'lib/daru_lite/index/multi_index.rb', line 326

def include?(tuple)
  return false unless tuple.is_a? Enumerable

  @labels[0...tuple.flatten.size]
    .transpose
    .include?(tuple.flatten.each_with_index.map { |e, i| @levels[i][e] })
end

#inspect(threshold = 20) ⇒ Object



356
357
358
359
# File 'lib/daru_lite/index/multi_index.rb', line 356

def inspect(threshold = 20)
  "#<DaruLite::MultiIndex(#{size}x#{width})>\n" +
    Formatters::Table.format([], headers: @name, row_headers: sparse_tuples, threshold: threshold)
end

#key(index) ⇒ Object

Raises:

  • (ArgumentError)


298
299
300
301
302
303
304
# File 'lib/daru_lite/index/multi_index.rb', line 298

def key(index)
  raise ArgumentError, "Key #{index} is too large" if index >= @labels[0].size

  @labels
    .each_with_index
    .map { |label, i| @levels[i].keys[label[index]] }
end

#levelsObject



13
14
15
# File 'lib/daru_lite/index/multi_index.rb', line 13

def levels
  @levels.map(&:keys)
end

#mapObject



7
8
9
# File 'lib/daru_lite/index/multi_index.rb', line 7

def map(&)
  to_a.map(&)
end

#pos(*indexes) ⇒ Object

Note:

If the arugent is both a valid index and a valid position, it will treated as valid index

Returns positions given indexes or positions

Examples:

idx = DaruLite::MultiIndex.from_tuples [[:a, :one], [:a, :two], [:b, :one], [:b, :two]]
idx.pos :a
# => [0, 1]

Parameters:

  • indexes (Array<object>)

    indexes or positions



155
156
157
158
159
160
161
162
163
164
165
# File 'lib/daru_lite/index/multi_index.rb', line 155

def pos(*indexes)
  if indexes.first.is_a? Integer
    return indexes.first if indexes.size == 1

    return indexes
  end
  res = self[*indexes]
  return res if res.is_a? Integer

  res.map { |i| self[i] }
end

#remove_layer(layer_index) ⇒ Object



261
262
263
264
265
266
267
# File 'lib/daru_lite/index/multi_index.rb', line 261

def remove_layer(layer_index)
  @levels.delete_at(layer_index)
  @labels.delete_at(layer_index)
  @name&.delete_at(layer_index)

  coerce_index
end

#reorder(new_order) ⇒ Object



212
213
214
215
# File 'lib/daru_lite/index/multi_index.rb', line 212

def reorder(new_order)
  from = to_a
  MultiIndex.from_tuples(new_order.map { |i| from[i] })
end

#sizeObject



334
335
336
# File 'lib/daru_lite/index/multi_index.rb', line 334

def size
  @labels[0].size
end

#sparse_tuplesObject

Return tuples with nils in place of repeating values, like this:

:a , :bar, :one
nil, nil , :two
nil, :foo, :one


382
383
384
385
386
387
388
# File 'lib/daru_lite/index/multi_index.rb', line 382

def sparse_tuples
  tuples = to_a
  [tuples.first] + each_cons(2).map do |prev, cur|
    left = cur.zip(prev).drop_while { |c, p| c == p }
    Array.new(cur.size - left.size) + left.map(&:first)
  end
end

#subset(*indexes) ⇒ Object



167
168
169
170
171
172
173
174
175
176
177
# File 'lib/daru_lite/index/multi_index.rb', line 167

def subset(*indexes)
  first_index = indexes.first
  if first_index.is_a? Integer
    MultiIndex.from_tuples(indexes.map { |index| key(index) })
  elsif first_index.is_a?(Array) && include?(first_index)
    # Same logic as in DaruLite::Index#subset
    MultiIndex.from_tuples indexes
  else
    self[indexes].conform indexes
  end
end

#to_aObject



348
349
350
# File 'lib/daru_lite/index/multi_index.rb', line 348

def to_a
  (0...size).map { |e| key(e) }
end

#to_dfObject



390
391
392
# File 'lib/daru_lite/index/multi_index.rb', line 390

def to_df
  DaruLite::DataFrame.new(@name.zip(to_a.transpose).to_h)
end

#to_htmlObject



361
362
363
364
# File 'lib/daru_lite/index/multi_index.rb', line 361

def to_html
  path = File.expand_path('../iruby/templates/multi_index.html.erb', __dir__)
  ERB.new(File.read(path).strip).result(binding)
end

#try_retrieve_from_integer(int) ⇒ Object



217
218
219
# File 'lib/daru_lite/index/multi_index.rb', line 217

def try_retrieve_from_integer(int)
  @levels[0].key?(int) ? retrieve_from_tuple([int]) : int
end

#valid?(*indexes) ⇒ Boolean

Returns:

  • (Boolean)


139
140
141
142
143
144
145
# File 'lib/daru_lite/index/multi_index.rb', line 139

def valid?(*indexes)
  # FIXME: This is perhaps not a good method
  pos(*indexes)
  true
rescue IndexError
  false
end

#valuesObject



352
353
354
# File 'lib/daru_lite/index/multi_index.rb', line 352

def values
  Array.new(size) { |i| i }
end

#widthObject



338
339
340
# File 'lib/daru_lite/index/multi_index.rb', line 338

def width
  @levels.size
end

#|(other) ⇒ Object



314
315
316
# File 'lib/daru_lite/index/multi_index.rb', line 314

def |(other)
  MultiIndex.from_tuples(to_a | other.to_a)
end