The majority of data can easily be plotted on a graph with equal intervals on the axes, for example 1, 2, 3 or 100, 200, 300 etc.. Some data, typically that which increases or decreases exponentially, cannot comfortably be graphed on such a scale without squashing the data up so much at one end that it becomes incomprehensible. The solution to this problem is to use a logarithmic scale.

## The Problem

Consider the data in the following table. Graphing this data with equal axis intervals of, say, 100,000 would make the differences in the lower values indistinguishable, and a scale to show them distinctly would make the graph impossibly large.

Label | Data |
---|---|

1910 | 2 |

1920 | 6 |

1930 | 29 |

1940 | 84 |

1950 | 361 |

1960 | 622 |

1970 | 4106 |

1980 | 6951 |

1990 | 15994 |

2000 | 81022 |

2010 | 198240 |

2020 | 765008 |

## The Solution

To show the lower values distinctly but still fit all the data on a reasonably sized graph we need to plot the logarithms of the data rather than the data itself, using a scale which increases exponentially. Assuming we are using a base 10 scale, the increments on the axis would be 1, 10, 100, 1000 etc..

Let's look at the data again, this time including the logarithm (to base 10) of the data.

Label | Data | log10(Data) |
---|---|---|

1910 | 2 | 0.301030 |

1920 | 6 | 0.778151 |

1930 | 29 | 1.462398 |

1940 | 84 | 1.924279 |

1950 | 361 | 2.561101 |

1960 | 622 | 2.793790 |

1970 | 4106 | 3.613419 |

1980 | 6951 | 3.842047 |

1990 | 15994 | 4.203957 |

2000 | 81022 | 4.908603 |

2010 | 198240 | 5.297191 |

2020 | 765008 | 5.883666 |

We have now reduced the data to a range of approximately 0.3 to 5.8, which can comfortably be shown on a graph with an axis of perhaps 0 to 10. Note though that the axis will not be labeled 0-10, but instead with 10 (or whatever base we are using) to the power of 0 to 10, as shown in the following table.

Interval Values | Power Equation | Axis Label |
---|---|---|

6 | 10^{6} | 1000000 |

5 | 10^{5} | 100000 |

4 | 10^{4} | 10000 |

3 | 10^{3} | 1000 |

2 | 10^{2} | 100 |

1 | 10^{1} | 10 |

0 | 10^{0} | 1 |

For this project we will write a short program to create a logarithmic plot of the sample data shown above, and save it as an SVG file looking like this.

The sample data is only very approximately exponential but is still reduced to roughly a straight line when plotted here. If the data were exactly exponential the points on the logarithmic plot would be on an exact straight line, but would have an ever-increasing gradient if plotted on a interval scale.

This project uses the SVG library I wrote for an earlier post. I won't include that code here but you might wish to take a look at the post to get an idea how the SVG library works.

## Coding

Create a new folder somewhere and in it create the following empty files. You can download the source code as a zip or clone/download from Github if you prefer. Both also contain the SVG file.

- data.py
- logarithmicplot.py
- main.py

Source Code Links

Open data.py and enter the following.

datagenerator.py

def generate_data():

"""

Create a set of sample data suitable for demonstrating logarithmic plots.

"""

return ({"label":"1910","data":2},

{"label":"1920","data":6},

{"label":"1930","data":29},

{"label":"1940","data":84},

{"label":"1950","data":364},

{"label":"1960","data":622},

{"label":"1970","data":4106},

{"label":"1980","data":6951},

{"label":"1990","data":15994},

{"label":"2000","data":81022},

{"label":"2010","data":198240},

{"label":"2020","data":765008})

The generate_data function is a quick and dirty way of getting some data suitable for plotting on a logarithmic scale. We can now move on to writing the code to create the actual graph, so open logarithmicplot.py and enter this:

logarithmicplot.py

import math

import svg

def draw_logarithmic_plot(width, height, title, data, maxpower, filename):

"""

Create a logarithmic plot as an SVG file from the data and other values supplied as arguments.

Save SVG file to specified filename.

"""

topmargin = 64

bottommargin = 32

leftmargin = 86

rightmargin = 32

graph_height = height - topmargin - bottommargin

graph_width = width - leftmargin - rightmargin

pixels_per_unit_x = graph_width / (len(data) - 1)

pixels_per_unit_y = graph_height / maxpower

s = svg.SVG()

s.create(width, height)

s.fill("#FFFFFF")

# header text and border lines

s.text(width/2, 38, "sans-serif", 16, "#000000", "#000000", "middle", title)

s.line("#808080", 2, leftmargin, topmargin, leftmargin, height - bottommargin)

s.line("#808080", 2, leftmargin, height - bottommargin, width - rightmargin, height - bottommargin)

# y axis indexes and values

y = height - bottommargin

for power in range(0, maxpower + 1):

s.line("#808080", 1, leftmargin - 8, y, leftmargin, y)

s.text(leftmargin - 12, y + 4, "sans-serif", 10, "#000000", "#000000", "end", str(10 ** power))

y -= pixels_per_unit_y

# x axis indexes and values

x = leftmargin

for item in data:

s.line("#808080", 1, x, height - bottommargin, x, height - bottommargin + 8)

s.text(x, height - bottommargin + 24, "sans-serif", 10, "#000000", "#000000", "middle", str(item["label"]))

y = height - bottommargin - (math.log10(item["data"]) * pixels_per_unit_y)

s.circle("#0000FF", 0, "#0000FF", 3, x, y)

# finish off

s.finalize()

s.save(filename)

print("File saved")

In the draw_logarithmic_plot function we first create a few variables:

- topmargin, bottommargin, leftmargin and rightmargin - the sizes of the four margins in pixels
- graph_height and graph_width - the size of the actual graph inside the margins
- pixels_per_unit_x and pixels_per_unit_y - the number of pixels used to represent each unit of data

We can then create an SVG struct - refer to the SVG Library post if you want to know the full details of how this works - and then fill its background, which I have hardcoded as white. Next we draw the heading text and axis lines.

The main parts of this function consist of two for loops. The first of these draws the y-axis values and index marks. The second draws the x-axis values and index marks as well as small circles for the data itself.

That's the graph drawn so we then call finalize which basically just adds a closing tag, and then save to write the SVG to a file. Then we just write out a message to say the file has been created.

We can now try out the code so open main.py and enter this.

main.py

import math

import datagenerator

import logarithmicplot

def main():

print("---------------------")

print("| codedrome.com |")

print("| Logarithmic Plots |")

print("---------------------\n")

data = datagenerator.generate_data()

print_data(data)

logarithmicplot.draw_logarithmic_plot(720, 540, "Logarithmic Plot", data, 6, "logarithmicplot1.svg")

def print_data(data):

print("----------------------------\n| Label | Data | log10 |\n----------------------------")

for item in data:

print("| {:6s} | {:6d} | {:6.2f} |".format(item["label"], item["data"], math.log10(item["data"])))

print("----------------------------")

main()

The main function is very simple, it just grabs some data with our generate_data function and passes it to draw_logarithmic_plot. I have also written a short function to print out the data including labels and the data's base 10 logarithm, basically what is in Table 2 above.

The coding is finished so we can run it with this command...

Running the program

python3.7 main.py

...which gives us this.

Program Output

---------------------

| codedrome.com |

| Logarithmic Plots |

---------------------

----------------------------

| Label | Data | log10 |

----------------------------

| 1910 | 2 | 0.30 |

| 1920 | 6 | 0.78 |

| 1930 | 29 | 1.46 |

| 1940 | 84 | 1.92 |

| 1950 | 364 | 2.56 |

| 1960 | 622 | 2.79 |

| 1970 | 4106 | 3.61 |

| 1980 | 6951 | 3.84 |

| 1990 | 15994 | 4.20 |

| 2000 | 81022 | 4.91 |

| 2010 | 198240 | 5.30 |

| 2020 | 765008 | 5.88 |

----------------------------

File saved

The output itself isn't hugely exciting but if you open the folder where you saved your source code you'll find a newly-created file called logarithmicplot1.svg, which you can double click to open with your default image viewer.

## Conclusion

This has been a very basic introduction to the rather esoteric topic of logarithmic plots, but I hope I have got the principles across sufficiently to give a foundation on which to build should you need to do so.

Follow @codedrome