Note: this tutorial is from the Daru visualization tutorial : http://nbviewer.jupyter.org/github/SciRuby/sciruby-notebooks/blob/master/Visualization/Visualizing%20data%20with%20daru%20DataFrame.ipynb
Using nyaplot in the background to generate interactive plots, which can be viewed in your browser.
In this tutorial we'll see how we can create some interesting plots with Daru::DataFrame using the Daru::View::Plot function.
require 'daru/view'
true
# Set a default plotting library
Daru::View.plotting_library = :nyaplot
:nyaplot
df = Daru::DataFrame.new({
a: Array.new(100) {|i| i},
b: 100.times.map{rand}
})
a | b | |
---|---|---|
0 | 0 | 0.9244635254291494 |
1 | 1 | 0.4078829961658763 |
2 | 2 | 0.575978367306516 |
3 | 3 | 0.8260751629090767 |
4 | 4 | 0.6290753555119412 |
5 | 5 | 0.7568707123738613 |
6 | 6 | 0.8725228089064007 |
7 | 7 | 0.810870990055066 |
8 | 8 | 0.7326539038221916 |
9 | 9 | 0.2123542488747251 |
10 | 10 | 0.7144330343441595 |
11 | 11 | 0.14346332617245683 |
12 | 12 | 0.5786029105402647 |
13 | 13 | 0.3623639916196123 |
14 | 14 | 0.08242104719908916 |
15 | 15 | 0.2684387487160035 |
16 | 16 | 0.4160352261437694 |
17 | 17 | 0.27689702370119307 |
18 | 18 | 0.19856906960264653 |
19 | 19 | 0.19201447633206215 |
20 | 20 | 0.6586007217656679 |
21 | 21 | 0.615596881481214 |
22 | 22 | 0.39103091156144076 |
23 | 23 | 0.9368145932060671 |
24 | 24 | 0.46141490224331483 |
25 | 25 | 0.3132275236441233 |
26 | 26 | 0.9065486301693224 |
27 | 27 | 0.9742477552526635 |
28 | 28 | 0.07253198100985081 |
29 | 29 | 0.2842567321582231 |
... | ... | ... |
99 | 99 | 0.9852550421331631 |
scatter_1 = Daru::View::Plot.new(df, type: :scatter, x: :a, y: :b)
#<Daru::View::Plot:0x000055880b36c548 @data=#<Daru::DataFrame(100x2)> a b 0 0 0.92446352 1 1 0.40788299 2 2 0.57597836 3 3 0.82607516 4 4 0.62907535 5 5 0.75687071 6 6 0.87252280 7 7 0.81087099 8 8 0.73265390 9 9 0.21235424 10 10 0.71443303 11 11 0.14346332 12 12 0.57860291 13 13 0.36236399 14 14 0.08242104 ... ... ..., @options={:type=>:scatter, :x=>:a, :y=>:b}, @user_options={}, @adapter=Daru::View::Adapter::NyaplotAdapter, @chart=#<Nyaplot::Plot:0x000055880b363f38 @properties={:diagrams=>[#<Nyaplot::Diagram:0x000055880b2265f8 @properties={:type=>:scatter, :options=>{:x=>:a, :y=>:b}, :data=>"19dabf3a-b845-46cd-a1d2-1ea2e3d42d22"}, @xrange=[0, 99], @yrange=[0.0011499619432241426, 0.9852550421331631]>], :options=>{}}>>
scatter_1.show_in_iruby
Just specifying the options to plot yields a very simple graph without much customization.
But what if you want to enhance your scatter plot with colors, add tooltips for each point and change the label of the X and Y axes. Also you may be faced with a situation where you want to see two different scatter plots on the same graph, each with a different color.
All this can be done by combining #plot with a block. The #plot method yields the corresponding Nyaplot::Plot and Nyaplot::Diagram objects for the graph, which can be used for many varied customizations. Lets see some examples:
# DataFrame denoting Ice Cream sales of a particular food chain in a city
# according to the maximum recorded temperature in that city. It also lists
# the staff strength present in each city.
df = Daru::DataFrame.new({
:temperature => [30.4, 23.5, 44.5, 20.3, 34, 24, 31.45, 28.34, 37, 24],
:sales => [350, 150, 500, 200, 480, 250, 330, 400, 420, 560],
:city => ['Pune', 'Delhi']*5,
:staff => [15,20]*5
})
df
temperature | sales | city | staff | |
---|---|---|---|---|
0 | 30.4 | 350 | Pune | 15 |
1 | 23.5 | 150 | Delhi | 20 |
2 | 44.5 | 500 | Pune | 15 |
3 | 20.3 | 200 | Delhi | 20 |
4 | 34 | 480 | Pune | 15 |
5 | 24 | 250 | Delhi | 20 |
6 | 31.45 | 330 | Pune | 15 |
7 | 28.34 | 400 | Delhi | 20 |
8 | 37 | 420 | Pune | 15 |
9 | 24 | 560 | Delhi | 20 |
# Generating a scatter plot with tool tips, colours and different shapes.
scatter_2 = Daru::View::Plot.new(df, type: :scatter, x: :temperature, y: :sales).chart
scatter_2.tap do |plot, diagram|
plot.x_label "Temperature"
plot.y_label "Sales"
plot.yrange [100, 600]
plot.xrange [15, 50]
plot.diagrams[0].tooltip_contents([:city, :staff])
plot.diagrams[0].color(Nyaplot::Colors.qual) # set the color scheme for this diagram. See Nyaplot::Colors for more info.
plot.diagrams[0].fill_by(:city) # Change color of each point WRT to the city that it belongs to.
plot.diagrams[0].shape_by(:city) # Shape each point WRT to the city that it belongs to.
end
# Move the mouse pointer over the points to see the tool tips.
# Array of diagrams
scatter_2.diagrams
[#<Nyaplot::Diagram:0x000055880a8d23d0 @properties={:type=>:scatter, :options=>{:x=>:temperature, :y=>:sales, :tooltip_contents=>[:city, :staff], :color=>#<Nyaplot::Color:0x000055880a8bde80 @source=["rgb(127,201,127)", "rgb(190,174,212)", "rgb(253,192,134)", "rgb(255,255,153)", "rgb(56,108,176)", "rgb(240,2,127)", "rgb(191,91,23)", "rgb(102,102,102)"]>, :fill_by=>:city, :shape_by=>:city}, :data=>"9559fc0a-55f0-4ef0-b80f-090a2030e01b"}, @xrange=[20.3, 44.5], @yrange=[150, 560]>]
Generating a bar graph requires passing :bar
into the :type
option.
# A Bar Graph denoting the age at which various Indian Kings died.
df = Daru::DataFrame.new({
name: ['Emperor Asoka', 'Akbar The Great', 'Rana Pratap', 'Shivaji Maharaj', 'Krishnadevaraya'],
age: [72,63,57,53,58]
}, order: [:name, :age])
df.sort!([:age])
name | age | |
---|---|---|
3 | Shivaji Maharaj | 53 |
2 | Rana Pratap | 57 |
4 | Krishnadevaraya | 58 |
1 | Akbar The Great | 63 |
0 | Emperor Asoka | 72 |
Daru::View::Plot.new(df, type: :bar, x: :name, y: :age).chart.tap do |plot|
plot.x_label "Name"
plot.y_label "Age"
plot.yrange [20,80]
end
It is also possible to simply pass in the :x
parameter if you want to the frequency of occurence of each element in a Vector.
a = ['A', 'C', 'G', 'T']
v = 1000.times.map { a.sample }
puts "v : ", v
df = Daru::DataFrame.new({
a: v
})
Daru::View::Plot.new(df, type: :bar, x: :a).chart.tap do |plot|
plot.yrange [0,350]
plot.y_label "Frequency"
plot.x_label "Letter"
end
v : ["A", "C", "T", "T", "A", "T", "C", "T", "G", "T", "G", "T", "T", "A", "T", "T", "T", "T", "G", "C", "T", "C", "G", "C", "T", "G", "T", "C", "G", "C", "T", "G", "G", "G", "T", "C", "T", "T", "A", "A", "A", "A", "A", "C", "C", "G", "T", "A", "T", "A", "A", "C", "C", "T", "C", "A", "C", "T", "T", "T", "G", "T", "C", "G", "A", "G", "C", "C", "G", "A", "C", "A", "A", "C", "A", "T", "G", "A", "T", "T", "A", "A", "A", "C", "G", "T", "C", "G", "C", "C", "T", "T", "C", "A", "T", "G", "G", "C", "A", "A", "T", "T", "T", "A", "T", "C", "A", "A", "A", "T", "A", "G", "G", "T", "A", "C", "C", "T", "T", "A", "G", "T", "G", "A", "A", "A", "C", "G", "C", "G", "G", "A", "T", "A", "T", "A", "G", "T", "A", "C", "G", "T", "A", "A", "C", "G", "C", "G", "T", "C", "T", "A", "G", "A", "C", "C", "C", "A", "C", "T", "C", "A", "G", "C", "C", "C", "T", "G", "T", "G", "A", "C", "C", "T", "C", "A", "G", "G", "T", "T", "T", "A", "C", "T", "A", "C", "A", "C", "T", "T", "T", "C", "C", "A", "C", "C", "C", "T", "T", "G", "T", "C", "C", "G", "T", "A", "C", "T", "T", "G", "A", "A", "C", "G", "A", "G", "C", "C", "C", "G", "C", "A", "G", "A", "T", "T", "T", "G", "A", "G", "G", "A", "T", "A", "C", "A", "C", "C", "C", "G", "A", "G", "G", "C", "A", "G", "G", "C", "T", "C", "A", "C", "A", "G", "G", "G", "C", "A", "T", "T", "C", "C", "G", "A", "A", "G", "G", "C", "A", "G", "G", "T", "C", "G", "A", "C", "A", "C", "A", "G", "G", "T", "G", "G", "G", "C", "C", "A", "T", "C", "T", "C", "A", "A", "C", "T", "G", "T", "A", "T", "T", "A", "G", "T", "C", "C", "G", "G", "T", "C", "T", "C", "T", "C", "G", "C", "T", "C", "T", "T", "A", "G", "G", "T", "C", "T", "G", "T", "T", "A", "T", "T", "G", "T", "A", "C", "C", "A", "A", "C", "C", "C", "C", "G", "T", "A", "A", "G", "C", "T", "C", "A", "A", "T", "G", "C", "A", "T", "T", "C", "C", "A", "T", "G", "C", "G", "C", "G", "G", "T", "A", "G", "A", "C", "T", "G", "T", "A", "T", "A", "G", "C", "C", "T", "G", "A", "C", "C", "T", "A", "G", "A", "T", "G", "T", "C", "C", "C", "T", "C", "C", "A", "G", "T", "A", "G", "C", "G", "G", "G", "C", "G", "C", "G", "A", "G", "T", "G", "T", "T", "G", "T", "T", "G", "G", "C", "A", "G", "T", "A", "A", "C", "A", "C", "C", "A", "C", "C", "G", "C", "T", "T", "C", "T", "G", "G", "C", "A", "C", "C", "A", "C", "T", "A", "A", "G", "C", "A", "T", "A", "C", "T", "T", "T", "C", "T", "A", "G", "G", "C", "C", "T", "C", "A", "C", "C", "C", "A", "T", "G", "A", "T", "T", "C", "A", "G", "C", "A", "A", "C", "C", "G", "T", "G", "A", "T", "C", "T", "G", "A", "T", "G", "A", "G", "A", "T", "A", "T", "C", "C", "G", "T", "T", "T", "T", "T", "G", "T", "G", "C", "A", "G", "A", "G", "C", "C", "C", "T", "C", "G", "C", "T", "A", "G", "T", "T", "G", "A", "C", "C", "A", "G", "A", "C", "A", "T", "A", "C", "T", "A", "A", "C", "A", "G", "T", "C", "G", "T", "A", "T", "G", "G", "T", "C", "C", "T", "A", "C", "C", "A", "T", "G", "A", "T", "A", "A", "C", "A", "G", "A", "C", "G", "G", "A", "A", "G", "G", "C", "A", "A", "G", "G", "A", "C", "A", "C", "G", "T", "C", "G", "G", "C", "G", "C", "T", "T", "G", "G", "A", "G", "G", "C", "A", "G", "G", "C", "C", "C", "G", "G", "T", "T", "A", "T", "G", "A", "T", "G", "C", "G", "A", "T", "G", "T", "T", "A", "T", "G", "G", "T", "G", "G", "C", "G", "G", "G", "T", "T", "T", "C", "C", "C", "A", "A", "C", "G", "G", "G", "G", "A", "G", "G", "A", "G", "A", "A", "T", "T", "C", "T", "A", "T", "A", "T", "A", "T", "T", "C", "C", "A", "G", "G", "C", "T", "C", "T", "A", "A", "A", "A", "A", "T", "G", "A", "A", "G", "A", "G", "A", "C", "A", "A", "C", "G", "A", "G", "G", "T", "A", "G", "T", "C", "T", "G", "G", "A", "C", "A", "T", "C", "G", "C", "G", "G", "G", "A", "T", "T", "A", "C", "A", "G", "T", "A", "C", "G", "T", "G", "T", "A", "T", "G", "G", "C", "G", "G", "A", "C", "T", "T", "T", "G", "G", "G", "C", "A", "G", "T", "C", "G", "G", "T", "A", "A", "T", "C", "T", "G", "A", "T", "A", "A", "A", "C", "C", "G", "G", "C", "A", "C", "C", "G", "C", "C", "A", "G", "C", "T", "G", "A", "T", "C", "G", "G", "G", "G", "T", "C", "G", "G", "G", "A", "C", "A", "C", "A", "A", "C", "G", "A", "T", "A", "C", "C", "A", "C", "C", "G", "G", "G", "T", "C", "C", "C", "A", "G", "T", "T", "C", "C", "G", "A", "C", "G", "C", "T", "G", "T", "C", "A", "C", "C", "T", "C", "G", "T", "C", "G", "T", "T", "T", "G", "C", "T", "G", "C", "G", "A", "C", "T", "C", "T", "A", "C", "G", "G", "T", "G", "C", "T", "C", "A", "C", "C", "C", "T", "A", "T", "T", "T", "G", "A", "C", "A", "T", "G", "T", "C", "G", "C", "C", "C", "A", "C", "G", "G", "C", "G", "A", "A", "T", "C", "C", "C", "G", "G", "C", "G", "A", "A", "T", "A", "G", "G", "C", "A", "A", "G", "C", "A", "G", "C", "C", "T", "G", "G", "G", "G", "T", "C", "A", "C", "G", "C", "T", "C", "G", "A", "G", "G", "A", "C", "C", "C", "G", "T", "T", "C", "A", "G", "A", "T", "C", "G", "G", "A", "A", "G", "T", "G", "G", "G", "C", "C", "G", "T", "G", "T", "A", "G", "G", "G", "C", "C", "C", "T", "A", "G", "T", "C", "T", "T", "A", "T", "A", "A", "A", "C", "G", "A", "A", "C", "A", "G", "C", "A", "G", "C", "A", "A"]
A box plot can be generated of the numerical vectors in the DataFrame by simply passing :box
to the :type
argument.
To demonstrate, I'll prepare some data using the distribution gem to get a bunch of normally distributed random variables. We'll then plot in a Box plot after creating a DataFrame with the data.
require 'distribution'
rng = Distribution::Normal.rng
# Daru.lazy_update = false
arr = []
1000.times {arr.push(rng.call)}
arr1 = arr.map{|val| val/0.8-2}
arr2 = arr.map{|val| val*1.1+0.3}
arr3 = arr.map{|val| val*1.3+0.3}
df = Daru::DataFrame.new({ a: arr, b: arr1, c: arr2, d: arr3 })
box_1 = Daru::View::Plot.new(df, type: :box)
box_1.show_in_iruby
Line graphs can be easily generated by passing :line
to the :type
option.
For example, lets plot a simple line graph showing the temperature of New York City over a week.
df = Daru::DataFrame.new({
temperature: [43,53,50,57,59,47],
day: [1,2,3,4,5,6]
})
line_1 = Daru::View::Plot.new(df,type: :line, x: :day, y: :temperature).chart
line_1.tap do |plot|
plot.x_label "Day"
plot.y_label "Temperature"
plot.yrange [20,60]
plot.xrange [1,6]
plot.legend true
plot.diagrams[0].title "Temperature in NYC"
end
Specify :histogram
to :type
will make a histogram from the data.
Histograms dont need a X axis label (because they show the frequency of elements in each bin) so you need to specify the name of the vector you want to plot by passing its name into the :x
option.
v = 1000.times.map { rand }
df = Daru::DataFrame.new({
a: v
})
Daru::View::Plot.new(df, type: :histogram, x: :a).chart.tap do |plot|
plot.yrange [0,150]
plot.y_label "Frequency"
plot.x_label "Bins"
end
Daru allows you to plot as many columns of your dataframe as you want on the same plot.
This can allow you to plot data from the dataframe onto the same graph and visually compare results from observations. You can individually set the color or point shape of each diagram on the plot.
As a first demostration, lets create a DataFrame of the temperatures of three different cities over the period of a week. Then, we'll plot them all on the same graph by passing options to the plot method which tell it the Vectors that are to be used for each of the diagrams.
df = Daru::DataFrame.new({
nyc_temp: [43,53,50,57,59,47],
chicago_temp: [23,30,35,20,26,38],
sf_temp: [60,65,73,67,55,52],
day: [1 ,2 ,3 ,4 ,5 , 6]
})
# As you can see, the options passed denote the x and y axes that are to be used by each diagram.
# You can add as many x any y axes as you want, just make sure the relevant vectors are present
# in your DataFrame!
#
# Heres an explanation of all the options passed:
#
# * type - The type of graph to be drawn. All the diagrams will be of the same type in this case.
# * x1/x2/x3 - The Vector from the DataFrame that is to be treated as the X axis for each of the
# three diagrams. In this case all of them need the :day Vector.
# * y1/y2/y3 - The Vector from the DataFrame that is to be treated as the Y axis for each of the
# three diagrams. As you can see the 1st diagram will plot nyc_temp, the 2nd chicago_temp and the
# the 3rd sf_temp.
#
# The values yielded in the block are also slightly different in this case.
# The first argument ('plot') is the same as in all the above examples (Nyaplot::Plot), but the
# second argument ('diagrams') is now an Array of Nyaplot::Diagram objects. Each of the elements
# in the Array represents the diagrams that you want to plot according to the sorting sequence
# of the options specifying the axes.
graph = Daru::View::Plot.new(df, type: :scatter, x1: :day, y1: :nyc_temp, x2: :day, y2: :chicago_temp, x3: :day, y3: :sf_temp)
graph.chart.tap do |plot|
nyc = plot.diagrams[0]
chicago = plot.diagrams[1]
sf = plot.diagrams[2]
nyc.title "Temprature in NYC"
nyc.color "#00FF00"
chicago.title "Temprature in Chicago"
chicago.color "#FFFF00"
sf.title "Temprature in SF"
sf.color "#0000FF"
plot.legend true
plot.yrange [0,100]
plot.x_label "Day"
plot.y_label "Temperature"
end
It is also possible to plot two different kinds of diagrams on the same plot. To show you how this works, I will plot a scatter graph and a line graph on the same plot.
To elaborate, we will be plotting the a set of points on a scatter plot alongwith their line of best fit.
df = Daru::DataFrame.new({
burger: ["Hamburger","Cheeseburger","Quarter Pounder","Quarter Pounder with Cheese","Big Mac","Arch Sandwich Special","Arch Special with Bacon","Crispy Chicken","Fish Fillet","Grilled Chicken","Grilled Chicken Light"],
fat: [9,13 ,21 ,30 ,31 ,31 ,34 ,25 ,28 ,20 ,5],
calories: [260,320,420,530,560,550,590,500,560,440,300]
},
order: [:burger, :fat, :calories])
burger | fat | calories | |
---|---|---|---|
0 | Hamburger | 9 | 260 |
1 | Cheeseburger | 13 | 320 |
2 | Quarter Pounder | 21 | 420 |
3 | Quarter Pounder with Cheese | 30 | 530 |
4 | Big Mac | 31 | 560 |
5 | Arch Sandwich Special | 31 | 550 |
6 | Arch Special with Bacon | 34 | 590 |
7 | Crispy Chicken | 25 | 500 |
8 | Fish Fillet | 28 | 560 |
9 | Grilled Chicken | 20 | 440 |
10 | Grilled Chicken Light | 5 | 300 |
We'll now write a small algorithm to compute the slope of the line of best fit by placing the fat content as the X co-ordinates and calories as Y co-ordinates.
The line of best fit will be a line graph of red color and the fat and calorie contents will be plotted as usual using a scatter plot.
# Algorithm for computing the line of best fit
sum_x = df[:fat].sum
sum2_x = (df[:fat]*df[:fat]).sum
sum_xy = (df[:fat]*df[:calories]).sum
mean_x = df[:fat].mean
mean_y = df[:calories].mean
slope = (sum_xy - sum_x * mean_y) / (sum2_x - sum_x * mean_x)
yint = mean_y - slope * mean_x
# Assign the computed Y co-ordinates of the line of best fit to a column
# in the DataFrame called :y_coords
df[:y_coords] = df[:fat].map {|f| f*slope + yint }
# As you can see the options passed into plot are slightly different this time.
#
# Instead of passing Vector names into :x1, :x2... separately, this time we pass
# the relevant names of the X and Y axes co-ordinates as an Array into the :x and
# :y options.This is a simpler and easier way to plot multiple diagrams.
#
# As is demonstrated in the previous example, the first argument yields a Nyaplot::Plot
# object and the second an Array of Nyaplot::Diagram objects. The diagrams are ordered
# according to the types specified in the `:type` option.
graph = Daru::View::Plot.new(df, type: [:scatter, :line], x: [:fat, :fat], y: [:calories, :y_coords])
graph.chart.tap do |plot|
plot.x_label "Fat"
plot.y_label "Calories"
plot.xrange [0,50]
scatter = plot.diagrams[0]
line = plot.diagrams[1]
line.color "#FF0000" #set color of the line to 'red'
scatter.tooltip_contents [:burger] # set tool tip to :burger
end