Line Chart – What it is ?
Line chart is a type of chart that shows data as a series of dots connected by a straight line. A line chart is one of the most basic ways to interpret financial and trade data. Let us go through a line chart in further detail, including its types, benefits, and drawbacks, as well as how to solve a few problems.
Key Features of Line Chart
- A line chart is a type of chart in which information is shown as a succession of data points linked by straight line segments.
- A line chart is a visual representation of an asset’s price history that uses a single, continuous line.
- A line chart is basic in design and easy to interpret, often representing merely changes in an asset’s closing price over time.
- Line charts remove noise from less crucial moments in the trading day, such as the open, high, and low prices, because they often only display closing prices.
- However, because to its simplicity, traders attempting to discover patterns or trends may choose chart styles with more information, such as a candlestick chart.
Creating and Customizing Line Chart
In this article, we will see in detail how we can create and customize line chart using matplotlib.
Importing necessary libraries:
We will be using functions from pyplot
package of matplotlib
library. We will refer that as plt
variable. Let us also import the numpy
library as np
, which is used to create and plot arrays in future.
import matplotlib.pyplot as plt import numpy as np
Passing lists as argument to plot:
We will pass list of year for x-axis and list of minimum temprature in that year
import matplotlib.pyplot as plt import numpy as np plt.plot([2011, 2012, 2013, 2014, 2015], [28, 31, 32, 27, 35]) plt.show()
This code will generate graph as shown here:
Passing variables as an argument:
Many times we might be reading data from file or we may be creating it by some functions. So often, data are stored in variables. Passing x-axis and y-axis data manually in function is not advisable. Let us modify the above code and store our data in variables. We use variable year
to store years for x-axis and variable minTemp
to use as y-axis data.
year = [2011, 2012, 2013, 2014, 2015] minTemp = [28, 31, 32, 27, 35] plt.plot(year, minTemp) plt.show()
Plotting multiple variables in same graph:
Assume that we would like to create chart of year wise minimum, maximum and average temprature. We can do this by calling plt.plot
function for earch (year, temprature)
pair
year = [2011, 2012, 2013, 2014, 2015] minTemp = [28, 31, 32, 27, 35] maxTemp = [38, 41, 43, 36, 39] avgTemp = [31, 37, 34, 32, 37] plt.plot(year, minTemp) plt.plot(year, maxTemp) plt.plot(year, avgTemp) plt.show()
Colors to the lines are assigned automatically by pyplot
unless we change it manually.
Adding legend to the chart
Three lines in the above chart do not convey any information. It is hard to find which line represents what data. Often legends
are attached to the graph to understand the data and plot relation.
year = [2011, 2012, 2013, 2014, 2015] minTemp = [28, 31, 32, 27, 35] maxTemp = [38, 41, 43, 36, 39] avgTemp = [31, 37, 34, 32, 37] plt.plot(year, minTemp, label = 'Minimum') plt.plot(year, maxTemp, label = 'Maximum') plt.plot(year, avgTemp, label = 'Average') plt.legend() plt.show()
Customizing legend:
We can change the position of the legend and we can also add the class for the legend. The legend in above graph represents temperature. We can add that additional information to make the chart more convincing using title
parameter. We can place legend at any position by specifying loc
parameters.
year = [2011, 2012, 2013, 2014, 2015] minTemp = [28, 31, 32, 27, 35] maxTemp = [38, 41, 43, 36, 39] avgTemp = [31, 37, 34, 32, 37] plt.plot(year, minTemp, label = 'Minimum') plt.plot(year, maxTemp, label = 'Maximum') plt.plot(year, avgTemp, label = 'Average') plt.legend(loc = (0.3, 0.05), title = 'Temprature', frameon = True) plt.show()
Customizing legend locations:
Apart from the absolute value, we passed for loc
parameter in the previous snippet, we can also use inbuilt positions. There are four different ways to place legend using inbuilt options:
upper left
upper right
lower left
lower right
We will demonstrate it using subplot
function as we have to show multiple plots. subplot
takes three arguments:
- number of raws (fixed)
- number of columns (fixed)
- plot number (variable)
year = [2011, 2012, 2013, 2014, 2015] minTemp = [28, 31, 32, 27, 35] maxTemp = [38, 41, 43, 36, 39] avgTemp = [31, 37, 34, 32, 37] legend_loc = ['upper left', 'upper right', 'lower left', 'lower right'] for i in range(4): plt.subplot(2, 2, i + 1) plt.plot(year, minTemp, label = 'Minimum') plt.plot(year, maxTemp, label = 'Maximum') plt.plot(year, avgTemp, label = 'Average') plt.legend(loc = legend_loc[i], title = 'Temprature') plt.show()
Set Axis Limits
As it can be seen in previous charts, legend is overlapping with graph content. This id due to auto axis limit set by pyplot
. using plt.axis([xmin, xmax, ymin, ymax])
function, we can set axis limit manually. By setting appropriate axis limit values, we can avoid the overlapping of legend with lines.
year = [2011, 2012, 2013, 2014, 2015] minTemp = [28, 31, 32, 27, 35] maxTemp = [38, 41, 43, 36, 39] avgTemp = [31, 37, 34, 32, 37] plt.plot(year, minTemp, label = 'Min. Temp') plt.plot(year, maxTemp, label = 'Max. Temp') plt.plot(year, avgTemp, label = 'Avg. Temp') plt.axis([2011, 2015, 15, 50]) plt.legend(title = 'Temprature', frameon = True) plt.show()
Annotating graph:
Can we add more customization? of course! Graphs without axis lable or title does not look complete. We can use plt.xlable( )
, plt.ylable( ) and plt.title( )
function to add X axis label, Y axis label and chart title, respectively.
year = [2011, 2012, 2013, 2014, 2015] minTemp = [28, 31, 32, 27, 35] maxTemp = [38, 41, 43, 36, 39] avgTemp = [31, 37, 34, 32, 37] plt.plot(year, minTemp, label = 'Minimum') plt.plot(year, maxTemp, label = 'Maximum') plt.plot(year, avgTemp, label = 'Average') plt.axis([2011, 2015, 15, 50]) plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Temprature') plt.legend(loc = 'lower right', title = 'Temprature', frameon = True) plt.show()
Setting up Grid:
When there are lot of data with multiple plots, its reasonable to add a grid to get a relative idea about the data. The grid helps to compare the data values on either axis. Using plt.grid( )
function we can add grid to the plot.
year = [2011, 2012, 2013, 2014, 2015] minTemp = [28, 31, 32, 27, 35] maxTemp = [38, 41, 43, 36, 39] avgTemp = [31, 37, 34, 32, 37] plt.plot(year, minTemp, label = 'Minimum') plt.plot(year, maxTemp, label = 'Maximum') plt.plot(year, avgTemp, label = 'Average') plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Temprature') plt.axis([2011, 2015, 15, 50]) plt.grid() plt.legend(loc = 'lower right', title = 'Temprature', frameon = True) plt.show()
Customizing Grid:
Almost every component in the chart is customizable. We can also customize the grid. The default color of the grid can be changed with the color
parameter. We can change the style of displaying grid lines using linestyle
parameter. Transparency of grid lines can be set using an alpha
parameter. Value of alpha varies from 0 to 1. alpha = 0
means completely transparent and alpha = 1
means completely opaque.
year = [2011, 2012, 2013, 2014, 2015] minTemp = [28, 31, 32, 27, 35] maxTemp = [38, 41, 43, 36, 39] avgTemp = [31, 37, 34, 32, 37] plt.plot(year, minTemp, label = 'Minimum') plt.plot(year, maxTemp, label = 'Maximum') plt.plot(year, avgTemp, label = 'Average') plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Temprature') plt.axis([2011, 2015, 15, 50]) plt.grid(which = 'major', color = 'blue', linestyle = '--', alpha = 0.2) plt.legend(loc = 'lower right', title = 'Temprature', frameon = True) plt.show()
Adding Minor Grid:
For a more detailed visual comparison, we can add a minor axis as well. We can set which = 'minor'
parameter in plt.grid( )
function to add minor grid in the plot.
year = [2011, 2012, 2013, 2014, 2015] minTemp = [28, 31, 32, 27, 35] maxTemp = [38, 41, 43, 36, 39] avgTemp = [31, 37, 34, 32, 37] plt.plot(year, minTemp, label = 'Minimum') plt.plot(year, maxTemp, label = 'Maximum') plt.plot(year, avgTemp, label = 'Average') plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Temprature') plt.axis([2011, 2015, 15, 50]) plt.grid(which = 'major', color = 'blue', linestyle = '--', alpha = 0.2) plt.minorticks_on() plt.grid(which = 'minor', color = 'red', linestyle = '--', alpha = 0.05) plt.legend(loc = 'lower right', title = 'Temprature', frameon = True) plt.show()
Adding xticks:
Many times the range shown on x axis would be too close or too far from each other. Using plt.xticks( )
, we can set appropriate ticks on x-axis. In the previous plot, the range on x-axis shows the value 2011.5, 2012.5, …, which is not realistic or not needed.
year = [2011, 2012, 2013, 2014, 2015] minTemp = [28, 31, 32, 27, 35] maxTemp = [38, 41, 43, 36, 39] avgTemp = [31, 37, 34, 32, 37] plt.plot(year, minTemp, label = 'Minimum') plt.plot(year, maxTemp, label = 'Maximum') plt.plot(year, avgTemp, label = 'Average') plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Temprature') plt.axis([2011, 2015, 15, 50]) plt.grid(which = 'major', color = 'blue', linestyle = '--', alpha = 0.2) plt.minorticks_on() plt.grid(which = 'minor', color = 'red', linestyle = '--', alpha = 0.05) plt.xticks(year) plt.legend(loc = 'lower right', title = 'Temprature', frameon = True) plt.show()
Adding yticks:
Like xticks, we can add yticks as well. Grid will auto adjust to tick values.
year = [2011, 2012, 2013, 2014, 2015] minTemp = [28, 31, 32, 27, 35] maxTemp = [38, 41, 43, 36, 39] avgTemp = [31, 37, 34, 32, 37] plt.plot(year, minTemp, label = 'Minimum') plt.plot(year, maxTemp, label = 'Maximum') plt.plot(year, avgTemp, label = 'Average') plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Temprature') plt.axis([2011, 2015, 15, 50]) plt.grid(which = 'major', color = 'blue', linestyle = '--', alpha = 0.2) plt.minorticks_on() plt.grid(which = 'minor', color = 'red', linestyle = '--', alpha = 0.05) plt.xticks(year) plt.yticks(minTemp) plt.legend(loc = 'lower right', title = 'Temprature', frameon = True) plt.show()
Customizing ticks:
We can change orientation, size and color of the tick values using rotation
, fontsize
and color
parameters.
year = [2011, 2012, 2013, 2014, 2015] minTemp = [28, 31, 32, 27, 35] maxTemp = [38, 41, 43, 36, 39] avgTemp = [31, 37, 34, 32, 37] plt.plot(year, minTemp, label = 'Minimum') plt.plot(year, maxTemp, label = 'Maximum') plt.plot(year, avgTemp, label = 'Average') plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Temprature') plt.axis([2011, 2015, 15, 50]) plt.grid(which = 'major', color = 'blue', linestyle = '--', alpha = 0.2) plt.minorticks_on() plt.grid(which = 'minor', color = 'red', linestyle = '--', alpha = 0.05) plt.xticks(year, rotation = 45, fontsize = 16, color = 'b') plt.yticks(minTemp, fontsize = 8, color = 'r') plt.legend(loc = 'lower right', title = 'Temprature', frameon = True) plt.show()
Demonstrating Colors:
In pyplot
, we can use various colors for different entities in the chart. Either we can specify the full name such as color = 'red'
or we can provide a shorter representation such as color = 'r'
. We can also specify the color in hexadecimal values, like color = 'ff0000'
.
year = [2011, 2012, 2013, 2014, 2015] Temp = np.array([28, 35, 30, 41, 35]) colors = ['r', 'g', 'b', 'c', 'm', 'y', 'k', '#123456', '#abcdef'] for i in range(len(colors)): plt.plot(year, Temp, color = colors[i]) Temp = Temp + 5 plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Temprature') plt.xticks(year) plt.show()
Line Width:
Like colors, we can also change the line width of the plot. We can specify linewidth = constant
to change the default setting.
year = [2011, 2012, 2013, 2014, 2015] Temp = np.array([28, 35, 30, 41, 35]) colors = ['r', 'g', 'b', 'c', 'm', 'y', 'k', '#123456', '#abcdef'] for i in range(len(colors)): plt.plot(year, Temp, color = colors[i], linewidth = i+1) Temp = Temp + 5 plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Temprature') plt.xticks(year) plt.show()
Transparency:
We can set the opacity of the line by setting alpha
parameter. Lower the alpha
value, the higher the transparency in the plotted object.
year = [2011, 2012, 2013, 2014, 2015] Temp = np.array([28, 35, 30, 41, 35]) colors = ['r', 'g', 'b', 'c', 'm', 'y', 'k', '#123456', '#abcdef'] for i in range(len(colors)): plt.plot(year, Temp, color = 'r', linewidth = 4, alpha = 1/(i + 1)) Temp = Temp + 5 plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Temprature') plt.xticks(year) plt.show()
Line Style:
There are four ways to change line style:
- Solid line:
linestyle = '-'
- Dahsed line:
linestyle = '--'
- Dashed-dot line:
linestyle = '-.'
- Point marker:
linestyle = ':'
year = [2011, 2012, 2013, 2014, 2015] Temp = np.array([28, 35, 30, 41, 35]) Style = ['-', '--', '-.', ':'] for i in range(len(Style)): plt.plot(year, Temp, color = 'r', linewidth = 1, linestyle = Style[i]) Temp = Temp + 5 plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Temprature') plt.xticks(year) plt.show()
Adding Markers:
In following graph we will see few more marker styles. We will examine the list of symbols [‘o’, ‘.’, ‘^’, ‘v’, ‘<‘, ‘>’, ‘1’, ‘2’, ‘3’, ‘4’] and we will see the corresponding markers on graph.
year = [2011, 2012, 2013, 2014, 2015] Temp = np.array([28, 35, 30, 41, 35]) marker = ['o', '.', '^', 'v', '<', '>', '1', '2', '3', '4'] for i in range(len(marker)): plt.plot(year, Temp, color = 'r', linestyle = '--', linewidth = 1, marker = marker[i]) Temp = Temp + 5 plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Temprature') plt.xticks(year) plt.show()
More markers:
In following graph we will see few more marker styles. We will examine the list of symbols ['8', 's', 'p', '*', 'h', 'H']
and we will see the corresponding markers on graph.
year = [2011, 2012, 2013, 2014, 2015] Temp = np.array([28, 35, 30, 41, 35]) marker = ['8', 's', 'p', '*', 'h', 'H'] for i in range(len(marker)): plt.plot(year, Temp, color = 'r', linestyle = '--', linewidth = 1, marker = marker[i], markersize = 10, markerfacecolor = 'c', markeredgewidth = 1) Temp = Temp + 5 plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Temprature') plt.xticks(year) plt.show()
In following graph we will see few more marker styles. We will examine the list of symbols [ 'x', 'D', 'd', '|', '_', 'P', 'X']
and we will see the corresponding markers on graph.
year = [2011, 2012, 2013, 2014, 2015] Temp = np.array([28, 35, 30, 41, 35]) marker = [ 'x', 'D', 'd', '|', '_', 'P', 'X'] for i in range(len(marker)): plt.plot(year, Temp, color = 'r', linestyle = '--', linewidth = 1, marker = marker[i], markersize = 10, markerfacecolor = 'c', markeredgewidth = 1) Temp = Temp + 5 plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Temprature') plt.xticks(year) plt.show()
Formatting using String:
Specifying color, marker and line style using three differenent parameters named color
, marker
and linestye
. And these three are very frequently used parameters, so instead of writing following code, pyplot allows us to use string format directly to be used with plot command.
plt.plot(x, y, color = 'r', marker = 'o', linestyle = '--')
is replaced by just
plt.plot(x, y, 'ro--')
This notation greatly simplifies the formatting of graph. We do not need to type keyword-parameter pair for all three effects.
year = [2011, 2012, 2013, 2014, 2015] Temp = np.array([28, 35, 30, 41, 35]) Format = ['ro--', 'b*-', 'kP-.', 'gP:', 'mH-'] for i in range(len(Format)): plt.plot(year, Temp, Format[i]) Temp = Temp + 5 plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Temprature') plt.xticks(year) plt.show()
Add Horizontal and Vertical Lines:
Sometimes it is good to show median or qurtile range of data on line plot. That will tell use something abour distribution. Manually we can add vertical and horizontal lines in the plot using following commands:
plt.axhline( )
: Add horizontal lineplt.axvline( )
: Add vertical line
As it is a line, we can tweak its appearance by adjusting appropriate parameters, which are demonstrated in the graph below:
year = [2011, 2012, 2013, 2014, 2015] minTemp = [28, 31, 32, 27, 35] maxTemp = [38, 41, 43, 36, 39] avgTemp = [31, 37, 34, 32, 37] plt.plot(year, minTemp, label = 'Minimum') plt.plot(year, maxTemp, label = 'Maximum') plt.plot(year, avgTemp, label = 'Average') plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Temprature') plt.axvline(2013, color = 'cyan', linewidth = 2, label = 'Median Age') plt.axhline(25, color = 'red', linewidth = 2, label = 'Median Salary') plt.axis([2011, 2015, 15, 50]) plt.show()
Subplots:
Often we need to compare multiple data. plt.subplot( )
allows to plot multiple plots on same canvas so that we can copare data without navigating to different graph windows. plt.subplot( r, c, index )
function takes three arguments:
r
: number of rowsc
: number of columnindex
: Index of the subgraph being plotted currently
This function creates r x c
subplots. in each subplot we can draw a graph of our choice.
year = [2011, 2012, 2013, 2014, 2015] minTemp = [28, 31, 32, 27, 35] maxTemp = [38, 41, 43, 36, 39] avgTemp = [31, 37, 34, 32, 37] plt.subplot(1, 3, 1) plt.plot(year, minTemp, label = 'Minimum') plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Minimum Temprature') plt.subplot(1, 3, 2) plt.plot(year, maxTemp, label = 'Maximum') plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. maximum Temprature') plt.subplot(1, 3, 3) plt.plot(year, avgTemp, label = 'Average') plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Average Temprature') plt.axis([2011, 2015, 15, 50]) plt.tight_layout() plt.show()
Plotting styles:
pyplot
provides many inbuilt templates for graph formatting. We can access all styles using plt.style.available
command.
plt.style.available
['Solarize_Light2', '_classic_test_patch', 'bmh', 'classic', 'dark_background', 'fast', 'fivethirtyeight', 'ggplot', 'grayscale', 'seaborn', 'seaborn-bright', 'seaborn-colorblind', 'seaborn-dark', 'seaborn-dark-palette', 'seaborn-darkgrid', 'seaborn-deep', 'seaborn-muted', 'seaborn-notebook', 'seaborn-paper', 'seaborn-pastel', 'seaborn-poster', 'seaborn-talk', 'seaborn-ticks', 'seaborn-white', 'seaborn-whitegrid', 'tableau-colorblind10']
Demonstration of Plotting Styles:
Let us demonstrate various inbuilt plotting styles with the help of subgraph. plt.tight_layout( )
adjust the distance between graphs and makes them look more visually appealing.
Styles = plt.style.available year = [11, 12, 13, 14, 15] Temp = [28, 31, 32, 27, 35] for i in range(25): plt.subplot(5, 5, i + 1) plt.plot(year, Temp, linewidth = 1) plt.xticks(year, fontsize = 8) plt.yticks([24, 28, 32, 34, 36], fontsize = 8) plt.style.use(Styles[i]) plt.title(Styles[i], fontsize = 8) plt.tight_layout() plt.show()
Seaborn Plotting Style:
Let us see how a graph looks with seaborb style. To set the seaborn style, we need to call the function plt.style.use('seaborn')
.
year = [2011, 2012, 2013, 2014, 2015] Temp = np.array([28, 35, 30, 41, 35]) Format = ['ro--', 'b*-', 'kP-.', 'gP:', 'mH-'] plt.plot(year, Temp) plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Temprature (Seaborn Style)') plt.xticks(year) plt.style.use('seaborn') plt.show()
Imperative vs. Object-Oriented Approach
matplotlib support two way of creating plots. Imperative appraoch and object oriented approach. Imperative approach is direct extension of MATLAB plot commands. So thoes who are comming with MATLAB background, imperative approach is suitable to them. Those who are comming from object oriented background, object oriented approach might suit them.
So far what ever plots we have created, they follow imperative approach. Follwoing exmaples demonstrate the usage of both the approaches. Thought syntax is different, output of both code snippets is same.
# IMPERATIVE APPROACH year = [2011, 2012, 2013, 2014, 2015] avgTemp = [32, 34, 36, 28, 40] # IMPERATIVE SYNTAX plt.figure(1) plt.clf() plt.plot(year, avgTemp) plt.xlabel('Year') plt.ylabel('Temprature') plt.title('Year vs. Average Temprature')
# OBJECT ORIENTED APPROACH year = [2011, 2012, 2013, 2014, 2015] avgTemp = [32, 34, 36, 28, 40] fig = plt.figure(2) fig.clf() ax = fig.add_subplot(1, 1, 1) ax.plot(year, avgTemp) ax.set_xlabel('Year'); ax.set_ylabel('Temprature'); ax.set_title('Year vs. Average Temprature');
Assignment with Solution
In the following video, I am giving tricks to master the customization of plots using MATPLOTLIB. I have created 20 custom tasks. On completing this set of tasks, you would surely be confident in any kind of customization in plots. Watch the video and try to implement tasks as explained. The complete solution is also provided for your reference, but it is expected that you try it first by yourself to develop an understanding.
You can download the dataset for the task set from here:
Download: testdata.csv
Solution:
import matplotlib.pyplot as plt import numpy as np import pandas as pd df = pd.read_csv('datasets/testdata.csv') Age = df['Age'] Salary = df['Salary'] / 1000 plt.rcParams["figure.figsize"] = (10, 8) plt.plot(Age, Salary, color = 'red', linewidth = 3, linestyle = '--', marker = 'o', markersize = 20, markerfacecolor = 'cyan', markeredgecolor = 'black', markeredgewidth = 3, label = 'Salary') plt.legend(loc = 'lower right', title = 'Age', frameon = False) plt.axis([22, 55, 40, 90]) plt.xlabel('Age of Employee', fontsize = 14, color = '#EC4E20') plt.ylabel('Salary of Employee (K)', fontsize = 14, color = '#EC4E20') plt.title('Age vs Salary (K)', fontsize = 18, color = 'blue') plt.grid(color='#55AF22', alpha=0.6) plt.minorticks_on() plt.grid(which = 'minor', color='#AC5742', alpha=0.2, linestyle = '--') plt.xticks(Age, rotation = 90, fontsize = 14) plt.yticks(Salary, fontsize = 14) plt.axvline(37, color = '#A36F63', linewidth = 5, linestyle = ':') plt.axhline(67, color = '#A36F63', linewidth = 5, linestyle = ':') plt.fill_between(Age, Salary, color = '#FE5F55', alpha = 0.2) plt.style.use('seaborn') plt.savefig('test.png') plt.show()
Output:
Test your knowledge:
You can also test your MATPLOTLIB skill by taking this simple quiz.
Test your knowledge: Start exam
Keep Learning !