require 'nyaplot'
require 'securerandom'
require 'json'
require 'csv'
module Nyaplot
begin
require "mikon"
rescue
end
if defined? Mikon
class DataFrame
include Nyaplot::Base
type :data
required_args :data
def initialize(*args)
super()
if args.length == 1 && args.first.is_a?(Mikon::DataFrame)
@df = args.first
else
@df = Mikon::DataFrame.new(*args)
end
data(@df)
end
def select!(*args, &block)
@df = @df.select(*args, &block)
data(@df)
self
end
def to_html
@df.to_html
end
def method_missing(name, *args, &block)
if @df.respond_to? name
ret = @df.send(name, *args, &block)
if ret.is_a?(Mikon::DataFrame)
self.class.new(ret)
else
ret
end
else
super
end
end
end
else
# Ruby DataFrame for plotting
class DataFrame
include Nyaplot::Base
type :data
required_args :data
DEFAULT_OPTS = {
:col_sep => ',',
:headers => true,
:converters => :numeric,
:header_converters => :symbol
}
attr_reader :rows
def initialize(source)
super()
@rows = []
case
when source.is_a?(Array)
# like [{a:10, b:10},{a:20,b:20}]
@rows = source
when source.is_a?(Hash)
# like {a:[10,20], b:[10, 20]}
keys = source.keys
len = source[keys[0]].length
(0..len-1).each do |i|
hash = {}
keys.each{|key| hash[key] = source[key][i]}
@rows.push(hash)
end
end
# transform String to Symbol as a key
unless @rows.all? {|row| row.keys.all? {|el| el.is_a?(Symbol)}}
@rows.map! do |row|
row.inject({}) do |hash, (key, val)|
hash[key.to_sym]=val
hash
end
end
end
end
def self.from_csv(*args)
path = args.shift
opts = DEFAULT_OPTS
if args.size > 0 && args.first.is_a?(Hash)
opts = opts.merge(args.shift)
else
opts[:col_sep] = args.shift if args.size > 0
opts[:headers] = args.shift if args.size > 0
end
csv = CSV.open(path, "r", opts)
yield csv if block_given?
rows = []
csv.each do |row|
hash = {}
row.each_with_index do |el,i|
next if el[0].nil? && el[1].nil?
hash[el[0].to_sym] = el[1]
end
rows << hash
end
self.new(rows)
end
# Filtering row out using recieved block
# @example
# new_df = df.filter{|row| row[:a] %2 == 0}
def filter(&block)
DataFrame.new(@rows.select(&block))
end
# destructive version of DataFrame#filter
def filter!(&block)
@rows.select!(&block)
end
# @return [String] the name of dataframe. If not specified when initializing, uuid v4 will be set.
def name
@uuid
end
def insert_column(name, arr)
name = name.is_a?(Symbol) ? name : name.to_sym
arr.each_with_index{|val, i| @rows[i][name]=val}
end
def delete_column(name)
name = name.is_a?(Symbol) ? name : name.to_sym
@rows.each do |row|
row.delete(name)
end
end
# Access column using its label
def column(name)
id = name.is_a?(Symbol) ? name : name.to_sym
column = @rows.map{|row| row[id]}
return Series.new(name, column)
end
# Insert row using index
# @param [Hash] row row to insert
# @param [Numeric] index if not specified, the row will be inserted to the end
def insert_row(row, index=@rows.length)
@rows.insert(index, row)
end
def row(index)
@rows[index]
end
def before_to_json
data(@rows)
end
def each_column(&block)
self.column_labels.each do |label|
block.call(column(label).to_a)
end
end
def each_row(&block)
@rows.each do |row|
block.call(row)
end
end
def to_html(threshold = 15)
html = '
'
unless @rows[0].nil?
html += ''
@rows[0].each {|key, val| html.concat('' + key.to_s + ' | ')}
html += '
'
end
@rows.each_with_index do |row, i|
next if i > threshold && i < @rows.length-1
html += ''
row.each{|key, val| html.concat('' + val.to_s + ' | ')}
html += '
'
if i == threshold
html += ''
row.length.times {html.concat('... | ')}
html += '
'
end
end
html += '
'
end
def to_s
to_html
end
# The alias method for DataFrame#column
def [](name)
return self.column(name)
end
def column_labels
@rows[0].keys
end
def method_missing(name, *args, &block)
if md = name.match(/(.+)\=/)
self.insert_column(name[/(.+)\=/].delete("="), args[0])
return
elsif column_labels.include?(name)
return self.column(name)
else
super(name, *args, &block)
end
end
end
class Series
include Enumerable
def each(&block)
@arr.each(&block)
end
def initialize(label, arr)
@arr = arr
@label = label
end
def to_html(threshold=15)
html = '' + label.to_s + ' |
>'
@arr.each_with_index do |el,i|
next if threshold < i && i < @arr.length-1
content = i == threshold ? '...' : el.to_s
html.concat('' + content + ' |
')
end
html += '
'
end
def to_json(*args)
@arr.to_json
end
def to_a
@arr
end
def label
@label
end
def method_missing(meth, *args, &block)
if @arr.respond_to?(meth)
@arr.send(meth, *args, &block)
else
super(meth, *args, &block)
end
end
def respond_to?(meth)
return true if @arr.respond_to?(meth)
super(meth)
end
end
end
end