How to flip the axis of a plot under ruby? - ruby

I wrote a ruby function to display the contents of a Daru dataframe df:
def plot_dataframe(df)
test = df.plot type: :line, x: :value, y: :depth
test.export_html(filename='test')
return
end
This outputs an html file named test.html.
How can I flip the y axis (ordinate) so that the depth starts at 0 and increases downwards?
I am looking for an equivalent to Python's invert_yaxis().
At #Serge de Gosson de Varennes' request, here is a MRE:
require 'json'
require 'daru'
require 'nyaplot'
df = Daru::DataFrame.new(
value: [1.2, 1.4, 1.1, 1.0, 1.0],
depth: [0, 1, 2, 3, 4]
)
test = df.plot type: :line, x: :value, y: :depth, y_reverse: true
test.export_html(filename='MRE')
This outputs:

You can do this in one of two ways:
def plot_dataframe(df)
test = df.plot type: :line, x: :value, y: :depth, y_reverse: true
test.export_html(filename='test')
return
end
or
def plot_dataframe(df)
test = df.plot type: :line, x: :value, y: :depth, y_axis_scale: :reverse
test.export_html(filename='test')
return
end

Related

YAML anchor for sequence?

Consider the following file YAML file:
A:
&B
- x: 1
y: 2
- x: 10
y: 20
C:
<<: *B
which is read into python via:
from ruamel.yaml import YAML
filename = 'data/configs/debug_config.yml'
with open(filename) as f:
c = YAML(typ='safe').load(f)
print(c)
yielding:
{'A': [{'x': 1, 'y': 2}, {'x': 10, 'y': 20}], 'C': {'x': 1, 'y': 2}}
It is seen that the anchor B only includes the first element of the sequence. Why? I would like an anchor that would include the entire sequence, such that the values of A and C in the python dictionary are identical. How can this be done?
The anchor B includes all elements from A, but you are merging them with the merge key << (source):
If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier in the sequence override keys specified in later mapping nodes.
So,the first item from A overrides the second one.
Remove <<: and C will be the same dictionary as A:
A: &B
- x: 1
y: 2
- x: 10
y: 20
C: *B
Yields:
{
"A": [
{
"y": 2,
"x": 1
},
{
"y": 20,
"x": 10
}
],
"C": [
{
"y": 2,
"x": 1
},
{
"y": 20,
"x": 10
}
]
}
You can check the merge order with this example:
A: &B
- x: 1
y: 2
- x: 10
new: 333
C:
<<: *B
Yields:
{
"A": [
{
"y": 2,
"x": 1
},
{
"x": 10,
"new": 333
}
],
"C": {
"y": 2,
"x": 1,
"new": 333
}
}
As #Hadyniak already indicated you are incorrectly using the merge
keys. Since the alias *B is expanded in the
composer step, before the interpretation of the << merge key in the
constructor step, the latter actually receives a list of dicts, which are
combined with the values of keys of earlier occuring dicts taking precedence. If
the order of processing had happened to be different you would get an error, and
IMO the merge key documentation doesn't explicitly specify that aliases should
be expanded first.
Hadyniak's solution however will have you end
up with c['A'] and c['C'] being the same dictionary and that might not be what you want:
import ruamel.yaml
yaml_str = """\
A:
&B
- x: 1
y: 2
- x: 10
y: 20
C:
*B
"""
yaml = ruamel.yaml.YAML()
c = yaml.load(yaml_str)
print(c['A'] is c['C'], c['A'] == c['C'])
c['A'][0]['x'] = 42
print('x:', c['C'][0]['x'])
which gives:
True True
x: 42
If that is not what you want, you can still use merge keys, but on the dictionaries that are the elements of your sequence:
import ruamel.yaml
yaml_str = """\
A:
- &B1
x: 1
y: 2
- &B2
x: 10
y: 20
C:
- <<: *B1
- <<: *B2
"""
yaml = ruamel.yaml.YAML()
c = yaml.load(yaml_str)
print(c['A'] is c['C'], c['A'] == c['C'])
c['A'][0]['x'] = 42
print('x:', c['C'][0]['x'])
which gives:
False True
x: 1
Alternatively you can tell the composer part of ruamel.yaml to expand the aliases instead of using a reference:
import copy
yaml_str = """\
A:
&B
- x: 1
y: 2
- x: 10
y: 20
C:
*B
"""
yaml = ruamel.yaml.YAML()
yaml.composer.return_alias = lambda s: copy.deepcopy(s)
c = yaml.load(yaml_str)
print(c['A'] is c['C'], c['A'] == c['C'])
c['A'][0]['x'] = 42
print('x:', c['C'][0]['x'])
which gives:
False True
x: 1
The above will only work in ruamel.yaml>0.17.2, for older versions you'll need to copy and modify the compose_node method:
import copy
yaml_str = """\
A:
&B
- x: 1
y: 2
- x: 10
y: 20
C:
*B
"""
class MyComposer(ruamel.yaml.composer.Composer):
def compose_node(self, parent, index):
# type: (Any, Any) -> Any
if self.parser.check_event(ruamel.yaml.events.AliasEvent):
event = self.parser.get_event()
alias = event.anchor
if alias not in self.anchors:
raise ComposerError(
None,
None,
'found undefined alias {alias!r}'.format(alias=alias),
event.start_mark,
)
return copy.deepcopy(self.anchors[alias])
event = self.parser.peek_event()
anchor = event.anchor
if anchor is not None: # have an anchor
if anchor in self.anchors:
ws = (
'\nfound duplicate anchor {!r}\nfirst occurrence {}\nsecond occurrence '
'{}'.format((anchor), self.anchors[anchor].start_mark, event.start_mark)
)
warnings.warn(ws, ruamel.yaml.error.ReusedAnchorWarning)
self.resolver.descend_resolver(parent, index)
if self.parser.check_event(ruamel.yaml.events.ScalarEvent):
node = self.compose_scalar_node(anchor)
elif self.parser.check_event(ruamel.yaml.events.SequenceStartEvent):
node = self.compose_sequence_node(anchor)
elif self.parser.check_event(ruamel.yaml.events.MappingStartEvent):
node = self.compose_mapping_node(anchor)
self.resolver.ascend_resolver()
return node
yaml = ruamel.yaml.YAML()
yaml.Composer = MyComposer
c = yaml.load(yaml_str)
print(c['A'] is c['C'], c['A'] == c['C'])
c['A'][0]['x'] = 42
print('x:', c['C'][0]['x'])
which also gives:
False True
x: 1

Ruby odd behavior of Array.each

class Point
attr_accessor :x, :y
def initialize(x =0, y = 0)
#x = x
#y = y
end
def to_s
"x: #{#x}; y: #{#y}"
end
def move(x,y)
#x = #x + x
#y = #y + y
end
end
my_point= Point.new(4,6)
puts my_point
my_point.move(7,14)
puts my_point
puts
my_square = Array.new(4, Point.new)
a_square = []
my_square.each {|i| puts i}
puts
my_square.each do|i|
b = i
b.move(2,4)
a_square<< b
end
a_square.each {|i| puts i}
The result
x: 4; y: 6
x: 11; y: 20
x: 0; y: 0
x: 0; y: 0
x: 0; y: 0
x: 0; y: 0
x: 8; y: 16
x: 8; y: 16
x: 8; y: 16
x: 8; y: 16
when it should be
x: 4; y: 6
x: 11; y: 20
x: 0; y: 0
x: 0; y: 0
x: 0; y: 0
x: 0; y: 0
x:2; y: 4
x:2; y: 4
x:2; y: 4
x:2; y: 4
Array.new(4, Point.new) will create an array with the same object (in this case an instance of Point).
my_square = Array.new(4, Point.new)
p my_square.map(&:object_id).uniq.count
#=> 1
If you change to Array.new(4) { Point.new }, this will populate array with different objects.
my_square = Array.new(4) { Point.new }
p my_square.map(&:object_id).uniq.count
#=> 4
Check this for more info.

How do I make my class objects return an array instead of <Classobject:0x007fc94a0...]>?

!!!! I'm cleaning up my code and rethinking my question. I'll repost and edited version in a few minutes. Thanks for the responses!
Here's my code:
class Student
attr_accessor :scores, :first_name
def initialize(first_name, scores)
#first_name = first_name
#scores = scores
end
def average
#scores.inject {|sum, el| sum + el }.to_f / #scores.size
end
def letter_grade
case average
when (90..100)
"A"
when (80..89)
"B"
when (70..79)
"C"
when (60..69)
"D"
when (0..59)
"F"
end
end
end
me = Student.new("Alex", [100,100,100,0,100])
student_2 = Student.new('Hodor', [2,2,7,0,90])
student_3 = Student.new('Aria', [90,100,87,90,90])
student_4 = Student.new('Tyrion', [95,100,97,100,30])
student_5 = Student.new('Jaela', [100,100,100,100,100])
students = [me, student_2, student_3, student_4, student_5]
p students
Here's what I get back:
[#<Student:0x007f92a91e6070 #first_name="Alex", #scores=[100, 100, 100, 0, 100]>, #<Student:0x007f92a91e5ff8 #first_name="Hodor", #scores=[2, 2, 7, 0, 90]>, #<Student:0x007f92a91e5f80 #first_name="Aria", #scores=[90, 100, 87, 90, 90]>, #<Student:0x007f92a91e5f08 #first_name="Tyrion", #scores=[95, 100, 97, 100, 30]>, #<Student:0x007f92a91e5e90 #first_name="Jaela", #scores=[100, 100, 100, 100, 100]>]
I want something like [["Alex", [100, 100, 100, 0, 100], ["Hodor", [2..]..]]
The goal is to have these tests pass:
p linear_search(students, "Alex") == 0
p linear_search(students, "NOT A STUDENT") == -1
So I actually need this to happen within the Student class, I think.
I'm not sure what's the purpose of the exercise, but to get from your actual output to your expected output, you just have to go over your elements, and build an array out of each one (use map):
students.map { |x| [x.first_name, x.scores] }
# => [["Alex", [100, 100, 100, 0, 100]], ["Hodor", [2, 2, 7, 0, 90]], ["Aria", [90, 100, 87, 90, 90]], ["Tyrion", [95, 100, 97, 100, 30]], ["Jaela", [100, 100, 100, 100, 100]]]
If you try to output an instance of Student, ruby calls to_s() on the Student instance. If your class does not provide a to_s() method, then the inherited to_s() method(in class Object) is called, which provides the string you see. If you redefine Object#to_s, you can prove that:
#Your code here
class Object
def to_s
'hello from Object#to_s'
end
end
p students
--output:--
[hello from Object#to_s,
hello from Object#to_s,
hello from Object#to_s,
hello from Object#to_s,
hello from Object#to_s]
If you override the to_s() method inside Student, then ruby will call it and use its return value whenever you try to output a Student object:
require 'pp'
class Student
attr_accessor :scores, :first_name
...
def to_s
"#{first_name} #{scores.inspect}"
end
end
students = [
Student.new("Alex", [100,100,100,0,100]),
Student.new('Hodor', [2,2,7,0,90]),
Student.new('Aria', [90,100,87,90,90]),
Student.new('Tyrion', [95,100,97,100,30]),
Student.new('Jaela', [100,100,100,100,100]),
]
pp students
--output:--
[Alex [100, 100, 100, 0, 100],
Hodor [2, 2, 7, 0, 90],
Aria [90, 100, 87, 90, 90],
Tyrion [95, 100, 97, 100, 30],
Jaela [100, 100, 100, 100, 100]]
In the code fragment scores.inspect the inspect() method is what p uses, i.e. p scores is equivalent to print scores.inspect + "\n". But you can't write:
"some string #{p.scores}"
because string interpolation uses the return value of p.scores, and p, like puts, always returns nil.

Find set of objects in array that have same attributes

Given that I have an array with two attributes: 'n_parents' and 'class', which looks like this:
my_arr = [{n_parents: 10, class: 'right'}, {n_parents: 10, class: 'right'}, {n_parents: 5, class: 'left'}, {n_parents: 2, class: 'center'}, {n_parents: 2, class: 'center'}, {n_parents: 2, class: 'center'}]
I would like to get an array with the objects that share most of those two attributes. So in the previous example:
result = [{n_parents: 2, class: 'center'}, {n_parents: 2, class: 'center'}, {n_parents: 2, class: 'center'}]
Because there are three objects that share n_parents = 2, and class = 'center'.
So far, I know how can I group by dos two attributes, but after that I am not sure how to get the set that has more elements.
Right now I have:
my_arr.group_by { |x| [x[:n_parents], x[:class]] }
This should work for you. It groups the hashes by the hash itself and then gets the largest group by the array count
my_arr = [{n_parents: 10, class: 'right'}, {n_parents: 10, class: 'right'}, {n_parents: 5, class: 'left'}, {n_parents: 2, class: 'center'}, {n_parents: 2, class: 'center'}, {n_parents: 2, class: 'center'}]
my_arr.group_by { |h| h }.max_by { |h,v| v.count }.last
#=>[{:n_parents=>2, :class=>"center"}, {:n_parents=>2, :class=>"center"}, {:n_parents=>2, :class=>"center"}]
something like below :
my_arr.group_by(&:values).max_by { |_,v| v.size }.last
# => [{:n_parents=>2, :class=>"center"},
# {:n_parents=>2, :class=>"center"},
# {:n_parents=>2, :class=>"center"}]
I am using code used by OP and extending over that to get result he wants:--
my_arr.group_by { |x| [x[:n_parents], x[:class]] }.max_by{|k,v| v.size}.last
Output
#=> [{:n_parents=>2, :class=>"center"}, {:n_parents=>2, :class=>"center"}, {:n_parents=>2, :class=>"center"}]
This is the fourth answer to be posted. The three earlier answers all employed group_by/max_by/last. Sure, that may be the best approach, but is it the most interesting, the most fun? Here are a couple other ways to generate the desired result. When
my_arr = [{n_parents: 10, class: 'right' }, {n_parents: 10, class: 'right' },
{n_parents: 5, class: 'left' }, {n_parents: 2, class: 'center'},
{n_parents: 2, class: 'center'}, {n_parents: 2, class: 'center'}]
the desired result is:
#=> [{:n_parents=>2, :class=>"center"},
# {:n_parents=>2, :class=>"center"},
# {:n_parents=>2, :class=>"center"}]
#1
# Create a hash `g` whose keys are the elements of `my_arr` (hashes)
# and whose values are counts for the elements of `my_arr`.
# `max_by` the values (counts) and construct the array.
el, nbr = my_arr.each_with_object({}) { |h,g| g[h] = (g[h] ||= 0) + 1 }
.max_by { |_,v| v }
arr = [el]*nbr
#2
# Sequentially delete the elements equal to the first element of `arr`,
# each time calculating the number of elements deleted, by determining
# `arr.size` before and after the deletion. Compare that number with the
# largest number deleted so far to find the element with the maximum
# number of instances in `arr`, then construct the array.
arr = my_arr.map(&:dup)
most_plentiful = { nbr_copies: 0, element: [] }
until arr.empty? do
sz = arr.size
element = arr.delete(arr.first)
if sz - arr.size > most_plentiful[:nbr_copies]
most_plentiful = { nbr_copies: sz - arr.size, element: element }
end
end
arr = [most_plentiful[:element]]* most_plentiful[:nbr_copies]

How to create custom iterator for Range

I'd like to create a subclass of Range in order to specify a step size other than 1 so I can do things like:
>> a = RangeWithStepSize.new(-1, 2, 0.5).each {|x| puts(x)}
-1.0
-0.5
0.0
0.5
1.0
1.5
2.0
=> -1..2
My first attempt at an implementation doesn't work:
class RangeWithStepSize < Range
attr_reader :step_size
def initialize(start_v, end_v, step_size, exclusive = false)
super(start_v, end_v, exclusive)
#step_size = step_size
end
def each
self.step(step_size).each
end
end
>> a = RangeWithStepSize.new(-1, 2, 0.5).each {|x| puts(x)}
=> #<Enumerator: [-1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]:each>
It appears that RangeWithStepSize#each is returning a valid enumerator, but it doesn't enumerate. Any idea why?
<aside>This may be related, but I notice is that Range#step without a block does NOT return an enumerator as specified in the documentation; it returns an array instead:
>> Range.new(-1, 2).step(0.5).class
=> Array
An Array is enumerable, but it is not an Enumerator. Is this a documentation bug?</aside>
clarification
I'd like to make a version of Range that encapsulates a step size, so I can do:
a = RangeWithStepSize(-1, 2, 0.5)
b = RangeWithStepSize(-1, 2, 0.25)
... so enumerating on a produces a step size of 0.5 and b produces 0.25.
You know you can do this, right? Inheritance isn't necessary here.
(-1..2).step(0.5) do |x|
puts x
end
Your code will work with a few small adjustments:
class RangeWithStepSize < Range
attr_reader :step_size
def initialize(start_v, end_v, step_size, exclusive = false)
super(start_v, end_v, exclusive)
#step_size = step_size
end
def each (&block)
self.step(step_size).each(&block)
end
end

Resources