Class: Daru::DataFrame

Inherits:
Object show all
Extended by:
Gem::Deprecate
Includes:
Maths::Arithmetic::DataFrame, Maths::Statistics::DataFrame, Plotting::DataFrame::NyaplotLibrary
Defined in:
lib/daru/dataframe.rb,
lib/daru/extensions/rserve.rb

Overview

rubocop:disable Metrics/ClassLength

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Plotting::DataFrame::NyaplotLibrary

#plot

Methods included from Maths::Statistics::DataFrame

#acf, #correlation, #count, #covariance, #cumsum, #describe, #ema, #max, #mean, #median, #min, #mode, #percent_change, #product, #range, #rolling_count, #rolling_max, #rolling_mean, #rolling_median, #rolling_min, #rolling_std, #rolling_variance, #standardize, #std, #sum, #variance_sample

Methods included from Maths::Arithmetic::DataFrame

#%, #*, #**, #+, #-, #/, #exp, #round, #sqrt

Constructor Details

#initialize(source, opts = {}) ⇒ DataFrame

DataFrame basically consists of an Array of Vector objects. These objects are indexed by row and column by vectors and index Index objects.

Arguments

  • source - Source from the DataFrame is to be initialized. Can be a Hash

of names and vectors (array or Daru::Vector), an array of arrays or array of Daru::Vectors.

Options

:order - An Array/*Daru::Index*/*Daru::MultiIndex* containing the order in which Vectors should appear in the DataFrame.

:index - An Array/*Daru::Index*/*Daru::MultiIndex* containing the order in which rows of the DataFrame will be named.

:name - A name for the DataFrame.

:clone - Specify as true or false. When set to false, and Vector objects are passed for the source, the Vector objects will not duplicated when creating the DataFrame. Will have no effect if Array is passed in the source, or if the passed Daru::Vectors have different indexes. Default to true.

Usage

df = Daru::DataFrame.new({a: [1,2,3,4], b: [6,7,8,9]}, order: [:b, :a],
  index: [:a, :b, :c, :d], name: :spider_man)

# =>
# <Daru::DataFrame:80766980 @name = spider_man @size = 4>
#             b          a
#  a          6          1
#  b          7          2
#  c          8          3
#  d          9          4


242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/daru/dataframe.rb', line 242

def initialize source, opts={} # rubocop:disable Metrics/MethodLength
  vectors, index = opts[:order], opts[:index] # FIXME: just keyword arges after Ruby 2.1
  @data = []
  @name = opts[:name]

  case source
  when ->(s) { s.empty? }
    @vectors = Index.coerce vectors
    @index   = Index.coerce index
    create_empty_vectors
  when Array
    initialize_from_array source, vectors, index, opts
  when Hash
    initialize_from_hash source, vectors, index, opts
  end

  set_size
  validate
  update
  self.plotting_library = Daru.plotting_library
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object



1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
# File 'lib/daru/dataframe.rb', line 1946

def method_missing(name, *args, &block)
  case
  when name =~ /(.+)\=/
    name = name[/(.+)\=/].delete('=')
    name = name.to_sym unless has_vector?(name)
    insert_or_modify_vector [name], args[0]
  when has_vector?(name)
    self[name]
  when has_vector?(name.to_s)
    self[name.to_s]
  else
    super
  end
end

Instance Attribute Details

#dataObject (readonly)

TOREMOVE



195
196
197
# File 'lib/daru/dataframe.rb', line 195

def data
  @data
end

#indexObject

The index of the rows of the DataFrame



198
199
200
# File 'lib/daru/dataframe.rb', line 198

def index
  @index
end

#nameObject (readonly)

The name of the DataFrame



201
202
203
# File 'lib/daru/dataframe.rb', line 201

def name
  @name
end

#sizeObject (readonly)

The number of rows present in the DataFrame



204
205
206
# File 'lib/daru/dataframe.rb', line 204

def size
  @size
end

#vectorsObject

The vectors (columns) index of the DataFrame



193
194
195
# File 'lib/daru/dataframe.rb', line 193

def vectors
  @vectors
end

Class Method Details

._load(data) ⇒ Object



1871
1872
1873
1874
1875
1876
1877
# File 'lib/daru/dataframe.rb', line 1871

def self._load data
  h = Marshal.load data
  Daru::DataFrame.new(h[:data],
    index: h[:index],
    order: h[:order],
    name:  h[:name])
end

.crosstab_by_assignation(rows, columns, values) ⇒ Object

Generates a new dataset, using three vectors

  • Rows

  • Columns

  • Values

For example, you have these values

x   y   v
a   a   0
a   b   1
b   a   1
b   b   0

You obtain

id  a   b
 a  0   1
 b  1   0

Useful to process outputs from databases



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/daru/dataframe.rb', line 151

def crosstab_by_assignation rows, columns, values
  raise 'Three vectors should be equal size' if
    rows.size != columns.size || rows.size!=values.size

  data = Hash.new { |h, col|
    h[col] = rows.factors.map { |r| [r, nil] }.to_h
  }
  columns.zip(rows, values).each { |c, r, v| data[c][r] = v }

  # FIXME: in fact, WITHOUT this line you'll obtain more "right"
  # data: with vectors having "rows" as an index...
  data = data.map { |c, r| [c, r.values] }.to_h
  data[:_id] = rows.factors

  DataFrame.new(data)
end

.from_activerecord(relation, *fields) ⇒ Object

Read a dataframe from AR::Relation

USE:

# When Post model is defined as:
class Post < ActiveRecord::Base
  scope :active, -> { where.not(published_at: nil) }
end

# You can load active posts into a dataframe by:
Daru::DataFrame.from_activerecord(Post.active, :title, :published_at)


95
96
97
# File 'lib/daru/dataframe.rb', line 95

def from_activerecord relation, *fields
  Daru::IO.from_activerecord relation, *fields
end

.from_csv(path, opts = {}, &block) ⇒ Object

Load data from a CSV file. Specify an optional block to grab the CSV object and pre-condition it (for example use the `convert` or `header_convert` methods).

Arguments

  • path - Path of the file to load specified as a String.

Options

Accepts the same options as the Daru::DataFrame constructor and CSV.open() and uses those to eventually construct the resulting DataFrame.

Verbose Description

You can specify all the options to the `.from_csv` function that you do to the Ruby `CSV.read()` function, since this is what is used internally.

For example, if the columns in your CSV file are separated by something other that commas, you can use the `:col_sep` option. If you want to convert numeric values to numbers and not keep them as strings, you can use the `:converters` option and set it to `:numeric`.

The `.from_csv` function uses the following defaults for reading CSV files (that are passed into the `CSV.read()` function):

{
  :col_sep           => ',',
  :converters        => :numeric
}


47
48
49
# File 'lib/daru/dataframe.rb', line 47

def from_csv path, opts={}, &block
  Daru::IO.from_csv path, opts, &block
end

.from_excel(path, opts = {}, &block) ⇒ Object

Read data from an Excel file into a DataFrame.

Arguments

  • path - Path of the file to be read.

Options

*:worksheet_id - ID of the worksheet that is to be read.



60
61
62
# File 'lib/daru/dataframe.rb', line 60

def from_excel path, opts={}, &block
  Daru::IO.from_excel path, opts, &block
end

.from_plaintext(path, fields) ⇒ Object

Read the database from a plaintext file. For this method to work, the data should be present in a plain text file in columns. See spec/fixtures/bank2.dat for an example.

Arguments

  • path - Path of the file to be read.

  • fields - Vector names of the resulting database.

Usage

df = Daru::DataFrame.from_plaintext 'spec/fixtures/bank2.dat', [:v1,:v2,:v3,:v4,:v5,:v6]


111
112
113
# File 'lib/daru/dataframe.rb', line 111

def from_plaintext path, fields
  Daru::IO.from_plaintext path, fields
end

.from_sql(dbh, query) ⇒ Object

Read a database query and returns a Dataset

USE:

dbh = DBI.connect("DBI:Mysql:database:localhost", "user", "password")
Daru::DataFrame.from_sql(dbh, "SELECT * FROM test")


75
76
77
# File 'lib/daru/dataframe.rb', line 75

def from_sql dbh, query
  Daru::IO.from_sql dbh, query
end

.rows(source, opts = {}) ⇒ Object

Create DataFrame by specifying rows as an Array of Arrays or Array of Daru::Vector objects.

Raises:



117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/daru/dataframe.rb', line 117

def rows source, opts={}
  raise SizeError, 'All vectors must have same length' \
    unless source.all? { |v| v.size == source.first.size }

  opts[:order] ||= guess_order(source)

  if ArrayHelper.array_of?(source, Array) || source.empty?
    DataFrame.new(source.transpose, opts)
  elsif ArrayHelper.array_of?(source, Vector)
    from_vector_rows(source, opts)
  else
    raise ArgumentError, "Can't create DataFrame from #{source}"
  end
end

Instance Method Details

#==(other) ⇒ Object



1921
1922
1923
1924
1925
1926
1927
# File 'lib/daru/dataframe.rb', line 1921

def == other
  self.class == other.class   &&
    @size    == other.size    &&
    @index   == other.index   &&
    @vectors == other.vectors &&
    @vectors.to_a.all? { |v| self[v] == other[v] }
end

#[](*names) ⇒ Object

Access row or vector. Specify name of row/vector followed by axis(:row, :vector). Defaults to :vector. Use of this method is not recommended for accessing rows. Use df.row for accessing row with index ':a'.



282
283
284
285
# File 'lib/daru/dataframe.rb', line 282

def [](*names)
  axis = extract_axis(names, :vector)
  dispatch_to_axis axis, :access, *names
end

#[]=(*args) ⇒ Object

Insert a new row/vector of the specified name or modify a previous row. Instead of using this method directly, use df.row = [1,2,3] to set/create a row ':a' to [1,2,3], or df.vector = [1,2,3] for vectors.

In case a Daru::Vector is specified after the equality the sign, the indexes of the vector will be matched against the row/vector indexes of the DataFrame before an insertion is performed. Unmatched indexes will be set to nil.



426
427
428
429
430
431
432
# File 'lib/daru/dataframe.rb', line 426

def []=(*args)
  vector = args.pop
  axis = extract_axis(args)
  names = args

  dispatch_to_axis axis, :insert_or_modify, names, vector
end

#_dump(_depth) ⇒ Object



1862
1863
1864
1865
1866
1867
1868
1869
# File 'lib/daru/dataframe.rb', line 1862

def _dump(_depth)
  Marshal.dump(
    data:  @data,
    index: @index.to_a,
    order: @vectors.to_a,
    name:  @name
  )
end

#add_row(row, index = nil) ⇒ Object



434
435
436
# File 'lib/daru/dataframe.rb', line 434

def add_row row, index=nil
  self.row[index || @size] = row
end

#add_vector(n, vector) ⇒ Object



438
439
440
# File 'lib/daru/dataframe.rb', line 438

def add_vector n, vector
  self[n] = vector
end

#add_vectors_by_split(name, join = '-', sep = Daru::SPLIT_TOKEN) ⇒ Object



1086
1087
1088
1089
1090
# File 'lib/daru/dataframe.rb', line 1086

def add_vectors_by_split(name,join='-',sep=Daru::SPLIT_TOKEN)
  self[name]
    .split_by_separator(sep)
    .each { |k,v| self["#{name}#{join}#{k}".to_sym] = v }
end

#add_vectors_by_split_recode(nm, join = '-', sep = Daru::SPLIT_TOKEN) ⇒ Object



1690
1691
1692
1693
1694
1695
1696
1697
# File 'lib/daru/dataframe.rb', line 1690

def add_vectors_by_split_recode(nm, join='-', sep=Daru::SPLIT_TOKEN)
  self[nm]
    .split_by_separator(sep)
    .each_with_index do |(k, v), i|
      v.rename "#{nm}:#{k}"
      self["#{nm}#{join}#{i + 1}".to_sym] = v
    end
end

#all?(axis = :vector, &block) ⇒ Boolean

Works like Array#all?

Examples:

Using all?

df = Daru::DataFrame.new({a: [1,2,3,4,5], b: ['a', 'b', 'c', 'd', 'e']})
df.all?(:row) do |row|
  row[:a] < 10
end #=> true


1143
1144
1145
1146
1147
1148
1149
1150
1151
# File 'lib/daru/dataframe.rb', line 1143

def all? axis=:vector, &block
  if axis == :vector || axis == :column
    @data.all?(&block)
  elsif axis == :row
    each_row.all?(&block)
  else
    raise ArgumentError, "Unidentified axis #{axis}"
  end
end

#any?(axis = :vector, &block) ⇒ Boolean

Works like Array#any?.

Examples:

Using any?

df = Daru::DataFrame.new({a: [1,2,3,4,5], b: ['a', 'b', 'c', 'd', 'e']})
df.any?(:row) do |row|
  row[:a] < 3 and row[:b] == 'b'
end #=> true


1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
# File 'lib/daru/dataframe.rb', line 1121

def any? axis=:vector, &block
  if axis == :vector || axis == :column
    @data.any?(&block)
  elsif axis == :row
    each_row do |row|
      return true if yield(row)
    end
    false
  else
    raise ArgumentError, "Unidentified axis #{axis}"
  end
end

#at(*positions) ⇒ Daru::Vector, Daru::DataFrame

Retrive vectors by positions

Examples:

df = Daru::DataFrame.new({
  a: [1, 2, 3],
  b: ['a', 'b', 'c']
})
df.at 0
# => #<Daru::Vector(3)>
#       a
#   0   1
#   1   2
#   2   3


364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# File 'lib/daru/dataframe.rb', line 364

def at *positions
  if AXES.include? positions.last
    axis = positions.pop
    return row_at(*positions) if axis == :row
  end

  original_positions = positions
  positions = coerce_positions(*positions, ncols)
  validate_positions(*positions, ncols)

  if positions.is_a? Integer
    @data[positions].dup
  else
    Daru::DataFrame.new positions.map { |pos| @data[pos].dup },
      index: @index,
      order: @vectors.at(*original_positions),
      name: @name
  end
end

#bootstrap(n = nil) ⇒ Daru::DataFrame

Creates a DataFrame with the random data, of n size. If n not given, uses original number of rows.



892
893
894
895
896
897
898
899
900
# File 'lib/daru/dataframe.rb', line 892

def bootstrap(n=nil)
  n ||= nrows
  Daru::DataFrame.new({}, order: @vectors).tap do |df_boot|
    n.times do
      df_boot.add_row(row[rand(n)])
    end
    df_boot.update
  end
end

#clone(*vectors_to_clone) ⇒ Object

Returns a 'view' of the DataFrame, i.e the object ID's of vectors are preserved.

Arguments

vectors_to_clone - Names of vectors to clone. Optional. Will return a view of the whole data frame otherwise.



478
479
480
481
482
483
484
# File 'lib/daru/dataframe.rb', line 478

def clone *vectors_to_clone
  vectors_to_clone.flatten! if ArrayHelper.array_of?(vectors_to_clone, Array)
  vectors_to_clone = @vectors.to_a if vectors_to_clone.empty?

  h = vectors_to_clone.map { |vec| [vec, self[vec]] }.to_h
  Daru::DataFrame.new(h, clone: false, order: vectors_to_clone, name: @name)
end

#clone_only_validObject

Returns a 'shallow' copy of DataFrame if missing data is not present, or a full copy of only valid data if missing data is present.



488
489
490
491
492
493
494
# File 'lib/daru/dataframe.rb', line 488

def clone_only_valid
  if include_values?(*Daru::MISSING_VALUES)
    reject_values(*Daru::MISSING_VALUES)
  else
    clone
  end
end

#clone_structureObject

Only clone the structure of the DataFrame.



467
468
469
# File 'lib/daru/dataframe.rb', line 467

def clone_structure
  Daru::DataFrame.new([], order: @vectors.dup, index: @index.dup, name: @name)
end

#collect(axis = :vector, &block) ⇒ Object

Iterate over a row or vector and return results in a Daru::Vector. Specify axis with :vector or :row. Default to :vector.

Description

The #collect iterator works similar to #map, the only difference being that it returns a Daru::Vector comprising of the results of each block run. The resultant Vector has the same index as that of the axis over which collect has iterated. It also accepts the optional axis argument.

Arguments

  • axis - The axis to iterate over. Can be :vector (or :column)

or :row. Default to :vector.



649
650
651
# File 'lib/daru/dataframe.rb', line 649

def collect axis=:vector, &block
  dispatch_to_axis_pl axis, :collect, &block
end

#collect_matrix::Matrix

Generate a matrix, based on vector names of the DataFrame.

:nocov: FIXME: Even not trying to cover this: I can't get, how it is expected to work.… – zverok



844
845
846
847
848
849
850
851
852
853
854
855
# File 'lib/daru/dataframe.rb', line 844

def collect_matrix
  return to_enum(:collect_matrix) unless block_given?

  vecs = vectors.to_a
  rows = vecs.collect { |row|
    vecs.collect { |col|
      yield row,col
    }
  }

  Matrix.rows(rows)
end

#collect_row_with_index(&block) ⇒ Object



818
819
820
821
822
# File 'lib/daru/dataframe.rb', line 818

def collect_row_with_index &block
  return to_enum(:collect_row_with_index) unless block_given?

  Daru::Vector.new(each_row_with_index.map(&block), index: @index)
end

#collect_rows(&block) ⇒ Object

Retrieves a Daru::Vector, based on the result of calculation performed on each row.



812
813
814
815
816
# File 'lib/daru/dataframe.rb', line 812

def collect_rows &block
  return to_enum(:collect_rows) unless block_given?

  Daru::Vector.new(each_row.map(&block), index: @index)
end

#collect_vector_with_index(&block) ⇒ Object



832
833
834
835
836
# File 'lib/daru/dataframe.rb', line 832

def collect_vector_with_index &block
  return to_enum(:collect_vector_with_index) unless block_given?

  Daru::Vector.new(each_vector_with_index.map(&block), index: @vectors)
end

#collect_vectors(&block) ⇒ Object

Retrives a Daru::Vector, based on the result of calculation performed on each vector.



826
827
828
829
830
# File 'lib/daru/dataframe.rb', line 826

def collect_vectors &block
  return to_enum(:collect_vectors) unless block_given?

  Daru::Vector.new(each_vector.map(&block), index: @vectors)
end

#compute(text, &block) ⇒ Object

Returns a vector, based on a string with a calculation based on vector.

The calculation will be eval'ed, so you can put any variable or expression valid on ruby.

For example:

a = Daru::Vector.new [1,2]
b = Daru::Vector.new [3,4]
ds = Daru::DataFrame.new({:a => a,:b => b})
ds.compute("a+b")
=> Vector [4,6]


1011
1012
1013
1014
# File 'lib/daru/dataframe.rb', line 1011

def compute text, &block
  return instance_eval(&block) if block_given?
  instance_eval(text)
end

#concat(other_df) ⇒ Object

Concatenate another DataFrame along corresponding columns. If columns do not exist in both dataframes, they are filled with nils



1252
1253
1254
1255
1256
1257
1258
1259
1260
# File 'lib/daru/dataframe.rb', line 1252

def concat other_df
  vectors = (@vectors.to_a + other_df.vectors.to_a).uniq

  data = vectors.map do |v|
    get_vector_anyways(v).dup.concat(other_df.get_vector_anyways(v))
  end

  Daru::DataFrame.new(data, order: vectors)
end

#create_sql(table, charset = 'UTF8') ⇒ Object

Create a sql, basen on a given Dataset

Arguments

  • table - String specifying name of the table that will created in SQL.

  • charset - Character set. Default is “UTF8”.

Examples:


ds = Daru::DataFrame.new({
 :id   => Daru::Vector.new([1,2,3,4,5]),
 :name => Daru::Vector.new(%w{Alex Peter Susan Mary John})
})
ds.create_sql('names')
 #=>"CREATE TABLE names (id INTEGER,\n name VARCHAR (255)) CHARACTER SET=UTF8;"


1715
1716
1717
1718
1719
1720
1721
1722
1723
# File 'lib/daru/dataframe.rb', line 1715

def create_sql(table,charset='UTF8')
  sql    = "CREATE TABLE #{table} ("
  fields = vectors.to_a.collect do |f|
    v = self[f]
    f.to_s + ' ' + v.db_type
  end

  sql + fields.join(",\n ")+") CHARACTER SET=#{charset};"
end

#delete_row(index) ⇒ Object

Delete a row

Raises:

  • (IndexError)


876
877
878
879
880
881
882
883
884
885
886
# File 'lib/daru/dataframe.rb', line 876

def delete_row index
  idx = named_index_for index

  raise IndexError, "Index #{index} does not exist." unless @index.include? idx
  @index = Daru::Index.new(@index.to_a - [idx])
  each_vector do |vector|
    vector.delete_at idx
  end

  set_size
end

#delete_vector(vector) ⇒ Object

Delete a vector

Raises:

  • (IndexError)


859
860
861
862
863
864
865
866
# File 'lib/daru/dataframe.rb', line 859

def delete_vector vector
  raise IndexError, "Vector #{vector} does not exist." unless @vectors.include?(vector)

  @data.delete_at @vectors[vector]
  @vectors = Daru::Index.new @vectors.to_a - [vector]

  self
end

#delete_vectors(*vectors) ⇒ Object

Deletes a list of vectors



869
870
871
872
873
# File 'lib/daru/dataframe.rb', line 869

def delete_vectors *vectors
  Array(vectors).each { |vec| delete_vector vec }

  self
end

#dup(vectors_to_dup = nil) ⇒ Object

Duplicate the DataFrame entirely.

Arguments

  • vectors_to_dup - An Array specifying the names of Vectors to

be duplicated. Will duplicate the entire DataFrame if not specified.



457
458
459
460
461
462
463
464
# File 'lib/daru/dataframe.rb', line 457

def dup vectors_to_dup=nil
  vectors_to_dup = @vectors.to_a unless vectors_to_dup

  src = vectors_to_dup.map { |vec| @data[@vectors[vec]].dup }
  new_order = Daru::Index.new(vectors_to_dup)

  Daru::DataFrame.new src, order: new_order, index: @index.dup, name: @name, clone: true
end

#dup_only_valid(vecs = nil) ⇒ Object

Creates a new duplicate dataframe containing only rows without a single missing value.



498
499
500
501
502
503
504
505
# File 'lib/daru/dataframe.rb', line 498

def dup_only_valid vecs=nil
  rows_with_nil = @data.map { |vec| vec.indexes(*Daru::MISSING_VALUES) }
                       .inject(&:concat)
                       .uniq

  row_indexes = @index.to_a
  (vecs.nil? ? self : dup(vecs)).row[*(row_indexes - rows_with_nil)]
end

#each(axis = :vector, &block) ⇒ Object

Iterate over each row or vector of the DataFrame. Specify axis by passing :vector or :row as the argument. Default to :vector.

Description

`#each` works exactly like Array#each. The default mode for `each` is to iterate over the columns of the DataFrame. To iterate over rows you must pass the axis, i.e `:row` as an argument.

Arguments

  • axis - The axis to iterate over. Can be :vector (or :column)

or :row. Default to :vector.



630
631
632
# File 'lib/daru/dataframe.rb', line 630

def each axis=:vector, &block
  dispatch_to_axis axis, :each, &block
end

#each_index(&block) ⇒ Object

Iterate over each index of the DataFrame.



564
565
566
567
568
569
570
# File 'lib/daru/dataframe.rb', line 564

def each_index &block
  return to_enum(:each_index) unless block_given?

  @index.each(&block)

  self
end

#each_rowObject

Iterate over each row



597
598
599
600
601
602
603
604
605
# File 'lib/daru/dataframe.rb', line 597

def each_row
  return to_enum(:each_row) unless block_given?

  @index.size.times do |pos|
    yield row_at(pos)
  end

  self
end

#each_row_with_indexObject



607
608
609
610
611
612
613
614
615
# File 'lib/daru/dataframe.rb', line 607

def each_row_with_index
  return to_enum(:each_row_with_index) unless block_given?

  @index.each do |index|
    yield access_row(index), index
  end

  self
end

#each_vector(&block) ⇒ Object Also known as: each_column

Iterate over each vector



573
574
575
576
577
578
579
# File 'lib/daru/dataframe.rb', line 573

def each_vector(&block)
  return to_enum(:each_vector) unless block_given?

  @data.each(&block)

  self
end

#each_vector_with_indexObject Also known as: each_column_with_index

Iterate over each vector alongwith the name of the vector



584
585
586
587
588
589
590
591
592
# File 'lib/daru/dataframe.rb', line 584

def each_vector_with_index
  return to_enum(:each_vector_with_index) unless block_given?

  @vectors.each do |vector|
    yield @data[@vectors[vector]], vector
  end

  self
end

#filter(axis = :vector, &block) ⇒ Object

Retain vectors or rows if the block returns a truthy value.

Description

For filtering out certain rows/vectors based on their values, use the #filter method. By default it iterates over vectors and keeps those vectors for which the block returns true. It accepts an optional axis argument which lets you specify whether you want to iterate over vectors or rows.

Arguments

  • axis - The axis to map over. Can be :vector (or :column) or :row.

Default to :vector.

Usage

# Filter vectors

df.filter do |vector|
  vector.type == :numeric and vector.median < 50
end

# Filter rows

df.filter(:row) do |row|
  row[:a] + row[:d] < 100
end


738
739
740
# File 'lib/daru/dataframe.rb', line 738

def filter axis=:vector, &block
  dispatch_to_axis_pl axis, :filter, &block
end

#filter_rowsObject

Iterates over each row and retains it in a new DataFrame if the block returns true for that row.



921
922
923
924
925
926
927
# File 'lib/daru/dataframe.rb', line 921

def filter_rows
  return to_enum(:filter_rows) unless block_given?

  keep_rows = @index.map { |index| yield access_row(index) }

  where keep_rows
end

#filter_vector(vec, &block) ⇒ Object

creates a new vector with the data of a given field which the block returns true



915
916
917
# File 'lib/daru/dataframe.rb', line 915

def filter_vector vec, &block
  Daru::Vector.new each_row.select(&block).map { |row| row[vec] }
end

#filter_vectors(&block) ⇒ Object

Iterates over each vector and retains it in a new DataFrame if the block returns true for that vector.



931
932
933
934
935
# File 'lib/daru/dataframe.rb', line 931

def filter_vectors &block
  return to_enum(:filter_vectors) unless block_given?

  dup.tap { |df| df.keep_vector_if(&block) }
end

#get_vector_anyways(v) ⇒ Object



1246
1247
1248
# File 'lib/daru/dataframe.rb', line 1246

def get_vector_anyways(v)
  @vectors.include?(v) ? self[v].to_a : [nil] * size
end

#group_by(*vectors) ⇒ Object

Group elements by vector to perform operations on them. Returns a Daru::Core::GroupBy object.See the Daru::Core::GroupBy docs for a detailed list of possible operations.

Arguments

  • vectors - An Array contatining names of vectors to group by.

Usage

df = Daru::DataFrame.new({
  a: %w{foo bar foo bar   foo bar foo foo},
  b: %w{one one two three two two one three},
  c:   [1  ,2  ,3  ,1    ,3  ,6  ,3  ,8],
  d:   [11 ,22 ,33 ,44   ,55 ,66 ,77 ,88]
})
df.group_by([:a,:b,:c]).groups
#=> {["bar", "one", 2]=>[1],
# ["bar", "three", 1]=>[3],
# ["bar", "two", 6]=>[5],
# ["foo", "one", 1]=>[0],
# ["foo", "one", 3]=>[6],
# ["foo", "three", 8]=>[7],
# ["foo", "two", 3]=>[2, 4]}


1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
# File 'lib/daru/dataframe.rb', line 1221

def group_by *vectors
  vectors.flatten!
  # FIXME: wouldn't it better to do vectors - @vectors here and
  # raise one error with all non-existent vector names?.. - zverok, 2016-05-18
  vectors.each { |v|
    raise(ArgumentError, "Vector #{v} does not exist") unless has_vector?(v)
  }

  vectors = [@vectors.first] if vectors.empty?

  Daru::Core::GroupBy.new(self, vectors)
end

#has_missing_data?Boolean Also known as: flawed?



1033
1034
1035
# File 'lib/daru/dataframe.rb', line 1033

def has_missing_data?
  !!@data.any? { |vec| vec.include_values?(*Daru::MISSING_VALUES) }
end

#has_vector?(vector) ⇒ Boolean

Check if a vector is present



1108
1109
1110
# File 'lib/daru/dataframe.rb', line 1108

def has_vector? vector
  @vectors.include? vector
end

#head(quantity = 10) ⇒ Object Also known as: first

The first ten elements of the DataFrame



1156
1157
1158
# File 'lib/daru/dataframe.rb', line 1156

def head quantity=10
  row.at 0..(quantity-1)
end

#include_values?(*values) ⇒ true, false

Check if any of given values occur in the data frame

Examples:

df = Daru::DataFrame.new({
  a: [1,    2,          3,   nil,        Float::NAN, nil, 1,   7],
  b: [:a,  :b,          nil, Float::NAN, nil,        3,   5,   8],
  c: ['a',  Float::NAN, 3,   4,          3,          5,   nil, 7]
}, index: 11..18)
df.include_values? nil
# => true


1052
1053
1054
# File 'lib/daru/dataframe.rb', line 1052

def include_values?(*values)
  @data.any? { |vec| vec.include_values?(*values) }
end

#inspect(spacing = 10, threshold = 15) ⇒ Object

Pretty print in a nice table format for the command line (irb/pry/iruby)



1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
# File 'lib/daru/dataframe.rb', line 1902

def inspect spacing=10, threshold=15
  row_headers = index.is_a?(MultiIndex) ? index.sparse_tuples : index.to_a
  name_part = @name ? ": #{@name} " : ''

  "#<#{self.class}#{name_part}(#{nrows}x#{ncols})>\n" +
    Formatters::Table.format(
      each_row.lazy,
      row_headers: row_headers,
      headers: vectors,
      threshold: threshold,
      spacing: spacing
    )
end

#interact_code(vector_names, full) ⇒ Object



1965
1966
1967
1968
1969
1970
1971
1972
1973
# File 'lib/daru/dataframe.rb', line 1965

def interact_code vector_names, full
  dfs = vector_names.zip(full).map do |vec_name, f|
    self[vec_name].contrast_code(full: f).each.to_a
  end

  all_vectors = recursive_product(dfs)
  Daru::DataFrame.new all_vectors,
    order: all_vectors.map(&:name)
end

#join(other_df, opts = {}) ⇒ Daru::DataFrame

Join 2 DataFrames with SQL style joins. Currently supports inner, left outer, right outer and full outer joins.

Examples:

Inner Join

left = Daru::DataFrame.new({
  :id   => [1,2,3,4],
  :name => ['Pirate', 'Monkey', 'Ninja', 'Spaghetti']
})
right = Daru::DataFrame.new({
  :id => [1,2,3,4],
  :name => ['Rutabaga', 'Pirate', 'Darth Vader', 'Ninja']
})
left.join(right, how: :inner, on: [:name])
#=>
##<Daru::DataFrame:82416700 @name = 74c0811b-76c6-4c42-ac93-e6458e82afb0 @size = 2>
#                 id_1       name       id_2
#         0          1     Pirate          2
#         1          3      Ninja          4


1638
1639
1640
# File 'lib/daru/dataframe.rb', line 1638

def join(other_df,opts={})
  Daru::Core::Merge.join(self, other_df, opts)
end

#keep_row_ifObject



902
903
904
905
906
# File 'lib/daru/dataframe.rb', line 902

def keep_row_if
  @index
    .reject { |idx| yield access_row(idx) }
    .each { |idx| delete_row idx }
end

#keep_vector_ifObject



908
909
910
911
912
# File 'lib/daru/dataframe.rb', line 908

def keep_vector_if
  @vectors.each do |vector|
    delete_vector(vector) unless yield(@data[@vectors[vector]], vector)
  end
end

#map(axis = :vector, &block) ⇒ Object

Map over each vector or row of the data frame according to the argument specified. Will return an Array of the resulting elements. To map over each row/vector and get a DataFrame, see #recode.

Description

The #map iterator works like Array#map. The value returned by each run of the block is added to an Array and the Array is returned. This method also accepts an axis argument, like #each. The default is :vector.

Arguments

  • axis - The axis to map over. Can be :vector (or :column) or :row.

Default to :vector.



669
670
671
# File 'lib/daru/dataframe.rb', line 669

def map axis=:vector, &block
  dispatch_to_axis_pl axis, :map, &block
end

#map!(axis = :vector, &block) ⇒ Object

Destructive map. Modifies the DataFrame. Each run of the block must return a Daru::Vector. You can specify the axis to map over as the argument. Default to :vector.

Arguments

  • axis - The axis to map over. Can be :vector (or :column) or :row.

Default to :vector.



681
682
683
684
685
686
687
# File 'lib/daru/dataframe.rb', line 681

def map! axis=:vector, &block
  if axis == :vector || axis == :column
    map_vectors!(&block)
  elsif axis == :row
    map_rows!(&block)
  end
end

#map_rows(&block) ⇒ Object

Map each row



788
789
790
791
792
# File 'lib/daru/dataframe.rb', line 788

def map_rows &block
  return to_enum(:map_rows) unless block_given?

  each_row.map(&block)
end

#map_rows!Object



800
801
802
803
804
805
806
807
808
# File 'lib/daru/dataframe.rb', line 800

def map_rows!
  return to_enum(:map_rows!) unless block_given?

  index.dup.each do |i|
    row[i] = should_be_vector!(yield(row[i]))
  end

  self
end

#map_rows_with_index(&block) ⇒ Object



794
795
796
797
798
# File 'lib/daru/dataframe.rb', line 794

def map_rows_with_index &block
  return to_enum(:map_rows_with_index) unless block_given?

  each_row_with_index.map(&block)
end

#map_vectors(&block) ⇒ Object

Map each vector and return an Array.



763
764
765
766
767
# File 'lib/daru/dataframe.rb', line 763

def map_vectors &block
  return to_enum(:map_vectors) unless block_given?

  @data.map(&block)
end

#map_vectors!Object

Destructive form of #map_vectors



770
771
772
773
774
775
776
777
778
# File 'lib/daru/dataframe.rb', line 770

def map_vectors!
  return to_enum(:map_vectors!) unless block_given?

  vectors.dup.each do |n|
    self[n] = should_be_vector!(yield(self[n]))
  end

  self
end

#map_vectors_with_index(&block) ⇒ Object

Map vectors alongwith the index.



781
782
783
784
785
# File 'lib/daru/dataframe.rb', line 781

def map_vectors_with_index &block
  return to_enum(:map_vectors_with_index) unless block_given?

  each_vector_with_index.map(&block)
end

#merge(other_df) ⇒ Daru::DataFrame

Merge vectors from two DataFrames. In case of name collision, the vectors names are changed to x_1, x_2 .…



1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
# File 'lib/daru/dataframe.rb', line 1592

def merge other_df # rubocop:disable Metrics/AbcSize
  unless nrows == other_df.nrows
    raise ArgumentError,
      "Number of rows must be equal in this: #{nrows} and other: #{other_df.nrows}"
  end

  new_fields = (@vectors.to_a + other_df.vectors.to_a)
  new_fields = ArrayHelper.recode_repeated(new_fields)

  DataFrame.new({}, order: new_fields).tap do |df_new|
    (0...nrows).each do |i|
      df_new.add_row row[i].to_a + other_df.row[i].to_a
    end

    df_new.update
  end
end

#missing_values_rows(missing_values = [nil]) ⇒ Object Also known as: vector_missing_values

Return a vector with the number of missing values in each row.

Arguments

  • missing_values - An Array of the values that should be

treated as 'missing'. The default missing value is nil.



1022
1023
1024
1025
1026
1027
1028
# File 'lib/daru/dataframe.rb', line 1022

def missing_values_rows missing_values=[nil]
  number_of_missing = each_row.map do |row|
    row.indexes(*missing_values).size
  end

  Daru::Vector.new number_of_missing, index: @index, name: "#{@name}_missing_rows"
end

#ncolsObject

The number of vectors



1103
1104
1105
# File 'lib/daru/dataframe.rb', line 1103

def ncols
  @vectors.size
end

#nest(*tree_keys, &_block) ⇒ Object

Return a nested hash using vector names as keys and an array constructed of hashes with other values. If block provided, is used to provide the values, with parameters row of dataset, current last hash on hierarchy and name of the key to include



1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
# File 'lib/daru/dataframe.rb', line 1060

def nest *tree_keys, &_block
  tree_keys = tree_keys[0] if tree_keys[0].is_a? Array

  each_row.each_with_object({}) do |row, current|
    # Create tree
    *keys, last = tree_keys
    current = keys.inject(current) { |c, f| c[row[f]] ||= {} }
    name = row[last]

    if block_given?
      current[name] = yield(row, current, name)
    else
      current[name] ||= []
      current[name].push(row.to_h.delete_if { |key,_value| tree_keys.include? key })
    end
  end
end

#nrowsObject

The number of rows



1098
1099
1100
# File 'lib/daru/dataframe.rb', line 1098

def nrows
  @index.size
end

#numeric_vector_namesObject



1396
1397
1398
# File 'lib/daru/dataframe.rb', line 1396

def numeric_vector_names
  @vectors.select { |v| self[v].numeric? }
end

#numeric_vectorsObject

Return the indexes of all the numeric vectors. Will include vectors with nils alongwith numbers.



1389
1390
1391
1392
1393
1394
# File 'lib/daru/dataframe.rb', line 1389

def numeric_vectors
  # FIXME: Why _with_index ?..
  each_vector_with_index
    .select { |vec, _i| vec.numeric? }
    .map(&:last)
end

#one_to_many(parent_fields, pattern) ⇒ Object

Creates a new dataset for one to many relations on a dataset, based on pattern of field names.

for example, you have a survey for number of children with this structure:

id, name, child_name_1, child_age_1, child_name_2, child_age_2

with

ds.one_to_many([:id], "child_%v_%n"

the field of first parameters will be copied verbatim to new dataset, and fields which responds to second pattern will be added one case for each different %n.

Examples:

cases=[
  ['1','george','red',10,'blue',20,nil,nil],
  ['2','fred','green',15,'orange',30,'white',20],
  ['3','alfred',nil,nil,nil,nil,nil,nil]
]
ds=Daru::DataFrame.rows(cases, order:
  [:id, :name,
   :car_color1, :car_value1,
   :car_color2, :car_value2,
   :car_color3, :car_value3])
ds.one_to_many([:id],'car_%v%n').to_matrix
#=> Matrix[
#   ["red", "1", 10],
#   ["blue", "1", 20],
#   ["green", "2", 15],
#   ["orange", "2", 30],
#   ["white", "2", 20]
#   ]


1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
# File 'lib/daru/dataframe.rb', line 1673

def one_to_many(parent_fields, pattern)
  vars, numbers = one_to_many_components(pattern)

  DataFrame.new([], order: [*parent_fields, '_col_id', *vars]).tap do |ds|
    each_row do |row|
      verbatim = parent_fields.map { |f| [f, row[f]] }.to_h
      numbers.each do |n|
        generated = one_to_many_row row, n, vars, pattern
        next if generated.values.all?(&:nil?)

        ds.add_row(verbatim.merge(generated).merge('_col_id' => n))
      end
    end
    ds.update
  end
end

#only_numerics(opts = {}) ⇒ Object

Return a DataFrame of only the numerical Vectors. If clone: false is specified as option, only a view of the Vectors will be returned. Defaults to clone: true.



1403
1404
1405
1406
1407
1408
1409
# File 'lib/daru/dataframe.rb', line 1403

def only_numerics opts={}
  cln = opts[:clone] == false ? false : true
  arry = numeric_vectors.map { |v| self[v] }

  order = Index.new(numeric_vectors)
  Daru::DataFrame.new(arry, clone: cln, order: order, index: @index)
end

#order=(order_array) ⇒ Object

Reorder the vectors in a dataframe

Examples:

df = Daru::DataFrame({
  a: [1, 2, 3],
  b: [4, 5, 6]
}, order: [:a, :b])
df.order = [:b, :a]
df
# => #<Daru::DataFrame(3x2)>
#       b   a
#   0   4   1
#   1   5   2
#   2   6   3

Raises:

  • (ArgumentError)


993
994
995
996
997
# File 'lib/daru/dataframe.rb', line 993

def order=(order_array)
  raise ArgumentError, 'Invalid order' unless
    order_array.sort == vectors.to_a.sort
  initialize(to_h, order: order_array)
end

#pivot_table(opts = {}) ⇒ Object

Pivots a data frame on specified vectors and applies an aggregate function to quickly generate a summary.

Options

:index - Keys to group by on the pivot table row index. Pass vector names contained in an Array.

:vectors - Keys to group by on the pivot table column index. Pass vector names contained in an Array.

:agg - Function to aggregate the grouped values. Default to :mean. Can use any of the statistics functions applicable on Vectors that can be found in the Daru::Statistics::Vector module.

:values - Columns to aggregate. Will consider all numeric columns not specified in :index or :vectors. Optional.

Usage

df = Daru::DataFrame.new({
  a: ['foo'  ,  'foo',  'foo',  'foo',  'foo',  'bar',  'bar',  'bar',  'bar'],
  b: ['one'  ,  'one',  'one',  'two',  'two',  'one',  'one',  'two',  'two'],
  c: ['small','large','large','small','small','large','small','large','small'],
  d: [1,2,2,3,3,4,5,6,7],
  e: [2,4,4,6,6,8,10,12,14]
})
df.pivot_table(index: [:a], vectors: [:b], agg: :sum, values: :e)

#=>
# #<Daru::DataFrame:88342020 @name = 08cdaf4e-b154-4186-9084-e76dd191b2c9 @size = 2>
#            [:e, :one] [:e, :two]
#     [:bar]         18         26
#     [:foo]         10         12

Raises:

  • (ArgumentError)


1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
# File 'lib/daru/dataframe.rb', line 1571

def pivot_table opts={}
  raise ArgumentError, 'Specify grouping index' if Array(opts[:index]).empty?

  index               = opts[:index]
  vectors             = opts[:vectors] || []
  aggregate_function  = opts[:agg] || :mean
  values              = prepare_pivot_values index, vectors, opts
  raise IndexError, 'No numeric vectors to aggregate' if values.empty?

  grouped = group_by(index)
  return grouped.send(aggregate_function) if vectors.empty?

  super_hash = make_pivot_hash grouped, vectors, values, aggregate_function

  pivot_dataframe super_hash
end

#plotting_library=(lib) ⇒ Object



264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/daru/dataframe.rb', line 264

def plotting_library= lib
  case lib
  when :gruff, :nyaplot
    @plotting_library = lib
    if Daru.send("has_#{lib}?".to_sym)
      extend Module.const_get(
        "Daru::Plotting::DataFrame::#{lib.to_s.capitalize}Library"
      )
    end
  else
    raise ArguementError, "Plotting library #{lib} not supported. "\
      'Supported libraries are :nyaplot and :gruff'
  end
end

#recast(opts = {}) ⇒ Object

Change dtypes of vectors by supplying a hash of :vector_name => :new_dtype

Usage

df = Daru::DataFrame.new({a: [1,2,3], b: [1,2,3], c: [1,2,3]})
df.recast a: :nmatrix, c: :nmatrix


1884
1885
1886
1887
1888
# File 'lib/daru/dataframe.rb', line 1884

def recast opts={}
  opts.each do |vector_name, dtype|
    self[vector_name].cast(dtype: dtype)
  end
end

#recode(axis = :vector, &block) ⇒ Object

Maps over the DataFrame and returns a DataFrame. Each run of the block must return a Daru::Vector object. You can specify the axis to map over. Default to :vector.

Description

Recode works similarly to #map, but an important difference between the two is that recode returns a modified Daru::DataFrame instead of an Array. For this reason, #recode expects that every run of the block to return a Daru::Vector.

Just like map and each, recode also accepts an optional axis argument.

Arguments

  • axis - The axis to map over. Can be :vector (or :column) or :row.

Default to :vector.



706
707
708
# File 'lib/daru/dataframe.rb', line 706

def recode axis=:vector, &block
  dispatch_to_axis_pl axis, :recode, &block
end

#recode_rowsObject



752
753
754
755
756
757
758
759
760
# File 'lib/daru/dataframe.rb', line 752

def recode_rows
  block_given? or return to_enum(:recode_rows)

  dup.tap do |df|
    df.each_row_with_index do |r, i|
      df.row[i] = should_be_vector!(yield(r))
    end
  end
end

#recode_vectorsObject



742
743
744
745
746
747
748
749
750
# File 'lib/daru/dataframe.rb', line 742

def recode_vectors
  block_given? or return to_enum(:recode_vectors)

  dup.tap do |df|
    df.each_vector_with_index do |v, i|
      df[*i] = should_be_vector!(yield(v))
    end
  end
end

#reindex(new_index) ⇒ Object

Change the index of the DataFrame and preserve the labels of the previous indexing. New index can be Daru::Index or any of its subclasses.

Examples:

Reindexing DataFrame

df = Daru::DataFrame.new({a: [1,2,3,4], b: [11,22,33,44]},
  index: ['a','b','c','d'])
#=>
##<Daru::DataFrame:83278130 @name = b19277b8-c548-41da-ad9a-2ad8c060e273 @size = 4>
#                    a          b
#         a          1         11
#         b          2         22
#         c          3         33
#         d          4         44
df.reindex Daru::Index.new(['b', 0, 'a', 'g'])
#=>
##<Daru::DataFrame:83177070 @name = b19277b8-c548-41da-ad9a-2ad8c060e273 @size = 4>
#                    a          b
#         b          2         22
#         0        nil        nil
#         a          1         11
#         g        nil        nil


1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
# File 'lib/daru/dataframe.rb', line 1308

def reindex new_index
  unless new_index.is_a?(Daru::Index)
    raise ArgumentError, 'Must pass the new index of type Index or its '\
      "subclasses, not #{new_index.class}"
  end

  cl = Daru::DataFrame.new({}, order: @vectors, index: new_index, name: @name)
  new_index.each_with_object(cl) do |idx, memo|
    memo.row[idx] = @index.include?(idx) ? row[idx] : [nil]*ncols
  end
end

#reindex_vectors(new_vectors) ⇒ Object



1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
# File 'lib/daru/dataframe.rb', line 1234

def reindex_vectors new_vectors
  unless new_vectors.is_a?(Daru::Index)
    raise ArgumentError, 'Must pass the new index of type Index or its '\
      "subclasses, not #{new_index.class}"
  end

  cl = Daru::DataFrame.new({}, order: new_vectors, index: @index, name: @name)
  new_vectors.each_with_object(cl) do |vec, memo|
    memo[vec] = @vectors.include?(vec) ? self[vec] : [nil]*nrows
  end
end

#reject_values(*values) ⇒ Daru::DataFrame

Returns a dataframe in which rows with any of the mentioned values

are ignored.

Examples:

df = Daru::DataFrame.new({
  a: [1,    2,          3,   nil,        Float::NAN, nil, 1,   7],
  b: [:a,  :b,          nil, Float::NAN, nil,        3,   5,   8],
  c: ['a',  Float::NAN, 3,   4,          3,          5,   nil, 7]
}, index: 11..18)
df.reject_values nil, Float::NAN
# => #<Daru::DataFrame(2x3)>
#       a   b   c
#   11   1   a   a
#   18   7   8   7


524
525
526
527
528
529
530
531
532
533
534
# File 'lib/daru/dataframe.rb', line 524

def reject_values(*values)
  positions =
    size.times.to_a - @data.flat_map { |vec| vec.positions(*values) }
  # Handle the case when positions size is 1 and #row_at wouldn't return a df
  if positions.size == 1
    pos = positions.first
    row_at(pos..pos)
  else
    row_at(*positions)
  end
end

#rename(new_name) ⇒ Object Also known as: name=

Rename the DataFrame.



1809
1810
1811
1812
# File 'lib/daru/dataframe.rb', line 1809

def rename new_name
  @name = new_name
  self
end

#rename_vectors(name_map) ⇒ Object

Renames the vectors

Arguments

  • name_map - A hash where the keys are the exising vector names and

    the values are the new names.  If a vector is renamed
    to a vector name that is already in use, the existing
    one is overwritten.

Usage

df = Daru::DataFrame.new({ a: [1,2,3,4], b: [:a,:b,:c,:d], c: [11,22,33,44] })
df.rename_vectors :a => :alpha, :c => :gamma
df.vectors.to_a #=> [:alpha, :b, :gamma]


1379
1380
1381
1382
1383
1384
1385
# File 'lib/daru/dataframe.rb', line 1379

def rename_vectors name_map
  existing_targets = name_map.select { |k,v| k != v }.values & vectors.to_a
  delete_vectors(*existing_targets)

  new_names = vectors.to_a.map { |v| name_map[v] ? name_map[v] : v }
  self.vectors = Daru::Index.new new_names
end

#replace_values(old_values, new_value) ⇒ Daru::DataFrame

Replace specified values with given value

Examples:

df = Daru::DataFrame.new({
  a: [1,    2,          3,   nil,        Float::NAN, nil, 1,   7],
  b: [:a,  :b,          nil, Float::NAN, nil,        3,   5,   8],
  c: ['a',  Float::NAN, 3,   4,          3,          5,   nil, 7]
}, index: 11..18)
df
# => #<Daru::DataFrame(8x3)>
#       a   b   c
#   11   1   a   a
#   12   2   b NaN
#   13   3 NaN   3
#   14 NaN NaN   4
#   15 NaN NaN   3
#   16 NaN   3   5
#   17   1   5 NaN
#   18   7   8   7


558
559
560
561
# File 'lib/daru/dataframe.rb', line 558

def replace_values old_values, new_value
  @data.each { |vec| vec.replace_values old_values, new_value }
  self
end

#report_building(b) ⇒ Object

:nodoc: #



1416
1417
1418
1419
1420
1421
1422
1423
1424
# File 'lib/daru/dataframe.rb', line 1416

def report_building(b) # :nodoc: #
  b.section(name: @name) do |g|
    g.text "Number of rows: #{nrows}"
    @vectors.each do |v|
      g.text "Element:[#{v}]"
      g.parse_element(self[v])
    end
  end
end

#respond_to_missing?(name, include_private = false) ⇒ Boolean



1961
1962
1963
# File 'lib/daru/dataframe.rb', line 1961

def respond_to_missing?(name, include_private=false)
  name.to_s.end_with?('=') || has_vector?(name) || super
end

#rowObject

Access a row or set/create a row. Refer #[] and #[]= docs for details.

Usage

df.row[:a] # access row named ':a'
df.row[:b] = [1,2,3] # set row ':b' to [1,2,3]


447
448
449
# File 'lib/daru/dataframe.rb', line 447

def row
  Daru::Accessors::DataFrameByRow.new(self)
end

#row_at(*positions) ⇒ Daru::Vector, Daru::DataFrame

Retrive rows by positions

Examples:

df = Daru::DataFrame.new({
  a: [1, 2, 3],
  b: ['a', 'b', 'c']
})
df.row_at 1, 2
# => #<Daru::DataFrame(2x2)>
#       a   b
#   1   2   b
#   2   3   c


300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/daru/dataframe.rb', line 300

def row_at *positions
  original_positions = positions
  positions = coerce_positions(*positions, nrows)
  validate_positions(*positions, nrows)

  if positions.is_a? Integer
    return Daru::Vector.new @data.map { |vec| vec.at(*positions) },
      index: @vectors
  else
    new_rows = @data.map { |vec| vec.at(*original_positions) }
    return Daru::DataFrame.new new_rows,
      index: @index.at(*original_positions),
      order: @vectors
  end
end

#save(filename) ⇒ Object

Use marshalling to save dataframe to a file.



1858
1859
1860
# File 'lib/daru/dataframe.rb', line 1858

def save filename
  Daru::IO.save self, filename
end

#set_at(positions, vector) ⇒ Object

Set vectors by positions

Examples:

df = Daru::DataFrame.new({
  a: [1, 2, 3],
  b: ['a', 'b', 'c']
})
df.set_at [0], ['x', 'y', 'z']
df
#=> #<Daru::DataFrame(3x2)>
#       a   b
#   0   x   a
#   1   y   b
#   2   z   c

Raises:



399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/daru/dataframe.rb', line 399

def set_at positions, vector
  if positions.last == :row
    positions.pop
    return set_row_at(positions, vector)
  end

  validate_positions(*positions, ncols)
  vector =
    if vector.is_a? Daru::Vector
      vector.reindex @index
    else
      Daru::Vector.new vector
    end

  raise SizeError, 'Vector length should match index length' if
    vector.size != @index.size

  positions.each { |pos| @data[pos] = vector }
end

#set_index(new_index, opts = {}) ⇒ Object

Set a particular column as the new DF

Raises:

  • (ArgumentError)


1276
1277
1278
1279
1280
1281
1282
1283
1284
# File 'lib/daru/dataframe.rb', line 1276

def set_index new_index, opts={}
  raise ArgumentError, 'All elements in new index must be unique.' if
    @size != self[new_index].uniq.size

  self.index = Daru::Index.new(self[new_index].to_a)
  delete_vector(new_index) unless opts[:keep]

  self
end

#set_row_at(positions, vector) ⇒ Object

Set rows by positions

Examples:

df = Daru::DataFrame.new({
  a: [1, 2, 3],
  b: ['a', 'b', 'c']
})
df.set_row_at [0, 1], ['x', 'x']
df
#=> #<Daru::DataFrame(3x2)>
#       a   b
#   0   x   x
#   1   x   x
#   2   3   c

Raises:



331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/daru/dataframe.rb', line 331

def set_row_at positions, vector
  validate_positions(*positions, nrows)
  vector =
    if vector.is_a? Daru::Vector
      vector.reindex @vectors
    else
      Daru::Vector.new vector
    end

  raise SizeError, 'Vector length should match row length' if
    vector.size != @vectors.size

  @data.each_with_index do |vec, pos|
    vec.set_at(positions, vector.at(pos))
  end
  @index = @data[0].index
  set_size
end

#shapeObject

Return the number of rows and columns of the DataFrame in an Array.



1093
1094
1095
# File 'lib/daru/dataframe.rb', line 1093

def shape
  [nrows, ncols]
end

#sort(vector_order, opts = {}) ⇒ Object

Non-destructive version of #sort!



1533
1534
1535
# File 'lib/daru/dataframe.rb', line 1533

def sort vector_order, opts={}
  dup.sort! vector_order, opts
end

#sort!(vector_order, opts = {}) ⇒ Object

Sorts a dataframe (ascending/descending) in the given pripority sequence of vectors, with or without a block.

Examples:

Sort a dataframe with a vector sequence.


df = Daru::DataFrame.new({a: [1,2,1,2,3], b: [5,4,3,2,1]})

df.sort [:a, :b]
# =>
# <Daru::DataFrame:30604000 @name = d6a9294e-2c09-418f-b646-aa9244653444 @size = 5>
#                   a          b
#        2          1          3
#        0          1          5
#        3          2          2
#        1          2          4
#        4          3          1

Sort a dataframe without a block. Here nils will be handled automatically.


df = Daru::DataFrame.new({a: [-3,nil,-1,nil,5], b: [4,3,2,1,4]})

df.sort([:a])
# =>
# <Daru::DataFrame:14810920 @name = c07fb5c7-2201-458d-b679-6a1f7ebfe49f @size = 5>
#                    a          b
#         1        nil          3
#         3        nil          1
#         0         -3          4
#         2         -1          2
#         4          5          4

Sort a dataframe with a block with nils handled automatically.


df = Daru::DataFrame.new({a: [nil,-1,1,nil,-1,1], b: ['aaa','aa',nil,'baaa','x',nil] })

df.sort [:b], by: {b: lambda { |a| a.length } }
# NoMethodError: undefined method `length' for nil:NilClass
# from (pry):8:in `block in __pry__'

df.sort [:b], by: {b: lambda { |a| a.length } }, handle_nils: true

# =>
# <Daru::DataFrame:28469540 @name = 5f986508-556f-468b-be0c-88cc3534445c @size = 6>
#                    a          b
#         2          1        nil
#         5          1        nil
#         4         -1          x
#         1         -1         aa
#         0        nil        aaa
#         3        nil       baaa

Sort a dataframe with a block with nils handled manually.


df = Daru::DataFrame.new({a: [nil,-1,1,nil,-1,1], b: ['aaa','aa',nil,'baaa','x',nil] })

# To print nils at the bottom one can use lambda { |a| (a.nil?)[1]:[0,a.length] }
df.sort [:b], by: {b: lambda { |a| (a.nil?)?[1]:[0,a.length] } }, handle_nils: true

# =>
#<Daru::DataFrame:22214180 @name = cd7703c7-1dca-4560-840b-5ea51a852ef9 @size = 6>
#                 a          b
#      4         -1          x
#      1         -1         aa
#      0        nil        aaa
#      3        nil       baaa
#      2          1        nil
#      5          1        nil

Options Hash (opts):

  • :ascending (TrueClass, FalseClass, Array) — default: true

    Sort in ascending or descending order. Specify Array corresponding to order for multiple sort orders.

  • :by (Hash) — default: lambda{|a| a }

    Specify attributes of objects to to be used for sorting, for each vector name in order as a hash of vector name and lambda expressions. In case a lambda for a vector is not specified, the default will be used.

  • :handle_nils (TrueClass, FalseClass, Array) — default: false

    Handle nils automatically or not when a block is provided. If set to True, nils will appear at top after sorting.

Raises:

  • (ArgumentError)


1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
# File 'lib/daru/dataframe.rb', line 1509

def sort! vector_order, opts={}
  raise ArgumentError, 'Required atleast one vector name' if vector_order.empty?

  # To enable sorting with categorical data,
  # map categories to integers preserving their order
  old = convert_categorical_vectors vector_order
  block = sort_prepare_block vector_order, opts

  order = @index.size.times.sort(&block)
  new_index = @index.reorder order

  # To reverse map mapping of categorical data to integers
  restore_categorical_vectors old

  @data.each do |vector|
    vector.reorder! order
  end

  self.index = new_index

  self
end

#split_by_category(cat_name) ⇒ Array

Split the dataframe into many dataframes based on category vector

Examples:

df = Daru::DataFrame.new({
  a: [1, 2, 3],
  b: ['a', 'a', 'b']
})
df.to_category :b
df.split_by_category :b
# => [#<Daru::DataFrame: a (2x1)>
#       a
#   0   1
#   1   2,
# #<Daru::DataFrame: b (1x1)>
#       a
#   2   3]

Raises:

  • (ArguementError)


1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
# File 'lib/daru/dataframe.rb', line 1993

def split_by_category cat_name
  cat_dv = self[cat_name]
  raise ArguementError, "#{cat_name} is not a category vector" unless
    cat_dv.category?

  cat_dv.categories.map do |cat|
    where(cat_dv.eq cat)
      .rename(cat)
      .delete_vector cat_name
  end
end

#summary(method = :to_text) ⇒ Object

Generate a summary of this DataFrame with ReportBuilder.



1412
1413
1414
# File 'lib/daru/dataframe.rb', line 1412

def summary(method=:to_text)
  ReportBuilder.new(no_title: true).add(self).send(method)
end

#tail(quantity = 10) ⇒ Object Also known as: last

The last ten elements of the DataFrame



1165
1166
1167
1168
# File 'lib/daru/dataframe.rb', line 1165

def tail quantity=10
  start = [-quantity, -size].max
  row.at start..-1
end

#to_aObject

Converts the DataFrame into an array of hashes where key is vector name and value is the corresponding element. The 0th index of the array contains the array of hashes while the 1th index contains the indexes of each row of the dataframe. Each element in the index array corresponds to its row in the array of hashes, which has the same index.



1763
1764
1765
# File 'lib/daru/dataframe.rb', line 1763

def to_a
  [each_row.map(&:to_h), @index.to_a]
end

#to_category(*names) ⇒ Daru::DataFrame

Converts the specified non category type vectors to category type vectors

Examples:

df = Daru::DataFrame.new({
  a: [1, 2, 3],
  b: ['a', 'a', 'b']
})
df.to_category :b
df[:b].type
# => :category


1941
1942
1943
1944
# File 'lib/daru/dataframe.rb', line 1941

def to_category *names
  names.each { |n| self[n] = self[n].to_category }
  self
end

#to_dfself

Returns the dataframe. This can be convenient when the user does not know whether the object is a vector or a dataframe.



1728
1729
1730
# File 'lib/daru/dataframe.rb', line 1728

def to_df
  self
end

#to_gslObject

Convert all numeric vectors to GSL::Matrix



1733
1734
1735
1736
1737
# File 'lib/daru/dataframe.rb', line 1733

def to_gsl
  numerics_as_arrays = numeric_vectors.map { |n| self[n].to_a }

  GSL::Matrix.alloc(*numerics_as_arrays.transpose)
end

#to_hObject

Converts DataFrame to a hash (explicit) with keys as vector names and values as the corresponding vectors.



1779
1780
1781
1782
1783
# File 'lib/daru/dataframe.rb', line 1779

def to_h
  @vectors
    .each_with_index
    .map { |vec_name, idx| [vec_name, @data[idx]] }.to_h
end

#to_html(threshold = 30) ⇒ Object

Convert to html for IRuby.



1786
1787
1788
1789
1790
1791
1792
1793
# File 'lib/daru/dataframe.rb', line 1786

def to_html threshold=30
  path = if index.is_a?(MultiIndex)
           File.expand_path('../iruby/templates/dataframe_mi.html.erb', __FILE__)
         else
           File.expand_path('../iruby/templates/dataframe.html.erb', __FILE__)
         end
  ERB.new(File.read(path).strip).result(binding)
end

#to_json(no_index = true) ⇒ Object

Convert to json. If no_index is false then the index will NOT be included in the JSON thus created.



1769
1770
1771
1772
1773
1774
1775
# File 'lib/daru/dataframe.rb', line 1769

def to_json no_index=true
  if no_index
    to_a[0].to_json
  else
    to_a.to_json
  end
end

#to_matrixObject

Convert all vectors of type :numeric into a Matrix.



1740
1741
1742
# File 'lib/daru/dataframe.rb', line 1740

def to_matrix
  Matrix.columns each_vector.select(&:numeric?).map(&:to_a)
end

#to_nmatrixObject

Convert all vectors of type :numeric and not containing nils into an NMatrix.



1752
1753
1754
1755
1756
# File 'lib/daru/dataframe.rb', line 1752

def to_nmatrix
  each_vector.select do |vector|
    vector.numeric? && !vector.include_values?(*Daru::MISSING_VALUES)
  end.map(&:to_a).transpose.to_nm
end

#to_nyaplotdfObject

Return a Nyaplot::DataFrame from the data of this DataFrame. :nocov:



1746
1747
1748
# File 'lib/daru/dataframe.rb', line 1746

def to_nyaplotdf
  Nyaplot::DataFrame.new(to_a[0])
end

#to_REXPObject

rubocop:disable Style/MethodName



5
6
7
8
9
10
11
12
13
# File 'lib/daru/extensions/rserve.rb', line 5

def to_REXP # rubocop:disable Style/MethodName
  names = @vectors.to_a
  data  = names.map do |f|
    Rserve::REXP::Wrapper.wrap(self[f].to_a)
  end
  l = Rserve::Rlist.new(data, names.map(&:to_s))

  Rserve::REXP.create_data_frame(l)
end

#to_sObject



1795
1796
1797
# File 'lib/daru/dataframe.rb', line 1795

def to_s
  to_html
end

#transposeObject

Transpose a DataFrame, tranposing elements and row, column indexing.



1891
1892
1893
1894
1895
1896
1897
1898
1899
# File 'lib/daru/dataframe.rb', line 1891

def transpose
  Daru::DataFrame.new(
    each_vector.map(&:to_a).transpose,
    index: @vectors,
    order: @index,
    dtype: @dtype,
    name: @name
  )
end

#union(other_df) ⇒ Object

Concatenates another DataFrame as #concat. Additionally it tries to preserve the index. If the indices contain common elements, #union will overwrite the according rows in the first dataframe.



1266
1267
1268
1269
1270
1271
1272
1273
# File 'lib/daru/dataframe.rb', line 1266

def union other_df
  index = (@index.to_a + other_df.index.to_a).uniq
  df = row[*(@index.to_a - other_df.index.to_a)]

  df = df.concat(other_df)
  df.index = Daru::Index.new(index)
  df
end

#updateObject

Method for updating the metadata (i.e. missing value positions) of the after assingment/deletion etc. are complete. This is provided so that time is not wasted in creating the metadata for the vector each time assignment/deletion of elements is done. Updating data this way is called lazy loading. To set or unset lazy loading, see the .lazy_update= method.



1804
1805
1806
# File 'lib/daru/dataframe.rb', line 1804

def update
  @data.each(&:update) if Daru.lazy_update
end

#vector_by_calculation(&block) ⇒ Object

DSL for yielding each row and returning a Daru::Vector based on the value each run of the block returns.

Usage

a1 = Daru::Vector.new([1, 2, 3, 4, 5, 6, 7])
a2 = Daru::Vector.new([10, 20, 30, 40, 50, 60, 70])
a3 = Daru::Vector.new([100, 200, 300, 400, 500, 600, 700])
ds = Daru::DataFrame.new({ :a => a1, :b => a2, :c => a3 })
total = ds.vector_by_calculation { a + b + c }
# <Daru::Vector:82314050 @name = nil @size = 7 >
#   nil
# 0 111
# 1 222
# 2 333
# 3 444
# 4 555
# 5 666
# 6 777


973
974
975
976
977
# File 'lib/daru/dataframe.rb', line 973

def vector_by_calculation &block
  a = each_row.map { |r| r.instance_eval(&block) }

  Daru::Vector.new a, index: @index
end

#vector_count_characters(vecs = nil) ⇒ Object



1078
1079
1080
1081
1082
1083
1084
# File 'lib/daru/dataframe.rb', line 1078

def vector_count_characters vecs=nil
  vecs ||= @vectors.to_a

  collect_rows do |row|
    vecs.map { |v| row[v].to_s.size }.inject(:+)
  end
end

#vector_mean(max_missing = 0) ⇒ Object

Calculate mean of the rows of the dataframe.

Arguments

  • max_missing - The maximum number of elements in the row that can be

zero for the mean calculation to happen. Default to 0.



1187
1188
1189
1190
1191
1192
1193
1194
1195
# File 'lib/daru/dataframe.rb', line 1187

def vector_mean max_missing=0
  # FIXME: in vector_sum we preserve created vector dtype, but
  # here we are not. Is this by design or ...? - zverok, 2016-05-18
  mean_vec = Daru::Vector.new [0]*@size, index: @index, name: "mean_#{@name}"

  each_row_with_index.each_with_object(mean_vec) do |(row, i), memo|
    memo[i] = row.indexes(*Daru::MISSING_VALUES).size > max_missing ? nil : row.mean
  end
end

#vector_sum(vecs = nil) ⇒ Object

Returns a vector with sum of all vectors specified in the argument. If vecs parameter is empty, sum all numeric vector.



1174
1175
1176
1177
1178
1179
# File 'lib/daru/dataframe.rb', line 1174

def vector_sum vecs=nil
  vecs ||= numeric_vectors
  sum = Daru::Vector.new [0]*@size, index: @index, name: @name, dtype: @dtype

  vecs.inject(sum) { |memo, n| memo + self[n] }
end

#verify(*tests) ⇒ Object

Test each row with one or more tests. Each test is a Proc with the form *Proc.new {|row| row > 0}*

The function returns an array with all errors.

FIXME: description here is too sparse. As far as I can get, it should tell something about that each test is [descr, fields, block], and that first value may be column name to output. - zverok, 2016-05-18



945
946
947
948
949
950
951
952
# File 'lib/daru/dataframe.rb', line 945

def verify(*tests)
  id = tests.first.is_a?(Symbol) ? tests.shift : @vectors.first

  each_row_with_index.map do |row, i|
    tests.reject { |*_, block| block.call(row) }
         .map { |test| verify_error_message row, test, id, i }
  end.flatten
end

#where(bool_array) ⇒ Object

Query a DataFrame by passing a Daru::Core::Query::BoolArray object.



1917
1918
1919
# File 'lib/daru/dataframe.rb', line 1917

def where bool_array
  Daru::Core::Query.df_where self, bool_array
end

#write_csv(filename, opts = {}) ⇒ Object

Write this DataFrame to a CSV file.

Arguements

  • filename - Path of CSV file where the DataFrame is to be saved.

Options

  • convert_comma - If set to true, will convert any commas in any

of the data to full stops ('.'). All the options accepted by CSV.read() can also be passed into this function.



1828
1829
1830
# File 'lib/daru/dataframe.rb', line 1828

def write_csv filename, opts={}
  Daru::IO.dataframe_write_csv self, filename, opts
end

#write_excel(filename, opts = {}) ⇒ Object

Write this dataframe to an Excel Spreadsheet

Arguments

  • filename - The path of the file where the DataFrame should be written.



1837
1838
1839
# File 'lib/daru/dataframe.rb', line 1837

def write_excel filename, opts={}
  Daru::IO.dataframe_write_excel self, filename, opts
end

#write_sql(dbh, table) ⇒ Object

Insert each case of the Dataset on the selected table

Arguments

  • dbh - DBI database connection object.

  • query - Query string.

Usage

ds = Daru::DataFrame.new({:id=>Daru::Vector.new([1,2,3]), :name=>Daru::Vector.new(["a","b","c"])})
dbh = DBI.connect("DBI:Mysql:database:localhost", "user", "password")
ds.write_sql(dbh,"test")


1853
1854
1855
# File 'lib/daru/dataframe.rb', line 1853

def write_sql dbh, table
  Daru::IO.dataframe_write_sql self, dbh, table
end