2

In R, we could plot each graph independently and then arrange all or some of the graphs easily by packages like gridExtra. For example,

p1 <- ggplot(aes(x1,y1), data=df) + geom_point()
p2 <- ggplot(aes(x2,y2), data=df) + geom_point()
p3 <- ggplot(aes(x3,y3), data=df) + geom_point()
p4 <- ggplot(aes(x4,y4), data=df) + geom_point()

I plot 4 graphs, and now I just want put 2 of them side by side to do some analysis, so I could

grid.arrange(p1, p2, ncol=1)
grid.arrange(p1, p3, ncol=1)
...

I find this is quite convenient for us to arbitrarily to combine and arrange independent graphs. However, can we do the same thing in Python with matplotlib? The big problem here is that I do not know how many graphs are there before hand and either how I want to combine and arrange them.

Alcott
  • 17,905
  • 32
  • 116
  • 173
  • See http://matplotlib.org/faq/usage_faq.html#coding-styles – tacaswell Oct 03 '14 at 04:12
  • and http://stackoverflow.com/questions/18284296/matplotlib-using-a-figure-object-to-initialize-a-plot/18302072#18302072 – tacaswell Oct 03 '14 at 04:13
  • and http://stackoverflow.com/questions/22606665/how-to-plot-2-subplots-from-different-functions-in-the-same-windowfigure/22612754#22612754 – tacaswell Oct 03 '14 at 04:14
  • @tcaswell, if I understand correctly, you mean create multiple `axes`, and then use these axes to draw stuff I want, right? Well if so, that means I should know how many axes I need and how they are arranged in one figure, but this is not the case I described in the post. – Alcott Oct 03 '14 at 05:00
  • also http://stackoverflow.com/questions/16750333/how-do-i-include-a-matplotlib-figure-object-as-subplot/16754215#16754215 – tacaswell Oct 03 '14 at 12:03
  • and https://github.com/yhat/ggplot is a project that aims to build an R-like interface on top of mpl – tacaswell Oct 03 '14 at 12:16

2 Answers2

0

Maybe gridspec would work for you? I use it to display/generate differents reports and summaries

http://matplotlib.org/users/gridspec.html

If not, maybe a simple wrapper for arbitrary comparisons?

import matplotlib.pyplot as plt

def compare(data, fig, rows, cols ):
    for i in range (0,len(data)):
        plt.figure(fig)
        plt.subplot(rows, cols, i+1)
        plt.plot(data[i])
    return

d1 = [1, 2, 3, 4]
d2 = [4, 3, 2, 1]
d3 = [4, 3, 3, 1]
d4 = [3, 4, 1, 2]
data = [d2,d1,d4]

# compare 4 horizontally
compare([d1, d2, d3, d4], fig=1, rows=1, cols=4)
# compare 4 vertically
compare([d1, d2, d3, d4], fig=2, rows=4, cols=1)
# compare 2 vertically
compare([d2, d3], fig=3, rows=2, cols=1)
# compare 3 horizontally
compare([d1, d2, d4], fig=4, rows=1, cols=3)
# compare 3 vertically
compare(data, fig=5, rows=3, cols=1)
plt.tight_layout()
plt.show()
0

In your same situation, I ended up doing my own function to create a grid of graphs. First, I save to disk all the graphs via matplotlib. Then, I create a collage of the graphs.


from typing import List, Tuple
from PIL import Image
import os

def find_multiples(number : int):
    multiples = set()
    for i in range(number - 1, 1, -1):
        mod = number % i
        if mod == 0:
            tup = (i, int(number / i))
            if tup not in multiples and (tup[1], tup[0]) not in multiples:
                multiples.add(tup)
                
    if len(multiples) == 0:
        mod == number % 2
        div = number // 2
        multiples.add((2, div + mod))
        
    return list(multiples)

def get_smallest_multiples(number : int, smallest_first = True) -> Tuple[int, int]:
    multiples = find_multiples(number)
    smallest_sum = number
    index = 0
    for i, m in enumerate(multiples):
        sum = m[0] + m[1]
        if sum < smallest_sum:
            smallest_sum = sum
            index = i
            
    result = list(multiples[i])
    if smallest_first:
        result.sort()
        
    return result[0], result[1]
    

def create_collage(listofimages : List[str], n_cols : int = 0, n_rows: int = 0, 
                   thumbnail_scale : float = 1.0, thumbnail_width : int = 0, thumbnail_height : int = 0):
    
    n_cols = n_cols if n_cols >= 0 else abs(n_cols)
    n_rows = n_rows if n_rows >= 0 else abs(n_rows)
    
    if n_cols == 0 and n_rows != 0:
        n_cols = len(listofimages) // n_rows
        
    if n_rows == 0 and n_cols != 0:
        n_rows = len(listofimages) // n_cols
        
    if n_rows == 0 and n_cols == 0:
        n_cols, n_rows = get_smallest_multiples(len(listofimages))
    
    thumbnail_width = 0 if thumbnail_width == 0 or n_cols == 0 else round(thumbnail_width / n_cols)
    thumbnail_height = 0 if thumbnail_height == 0 or n_rows == 0 else round(thumbnail_height/n_rows)
    
    all_thumbnails : List[Image.Image] = []
    for p in listofimages:
        thumbnail = Image.open(p)
        if thumbnail_width * thumbnail_scale < thumbnail.width:
            thumbnail_width = round(thumbnail.width * thumbnail_scale)
        if thumbnail_height * thumbnail_scale < thumbnail.height:
            thumbnail_height = round(thumbnail.height * thumbnail_scale)
        
        thumbnail.thumbnail((thumbnail_width, thumbnail_height))
        all_thumbnails.append(thumbnail)

    new_im = Image.new('RGB', (thumbnail_width * n_cols, thumbnail_height * n_rows), 'white')
    
    i, x, y = 0, 0, 0
    for col in range(n_cols):
        for row in range(n_rows):
            if i > len(all_thumbnails) - 1:
                continue
            
            print(i, x, y)
            new_im.paste(all_thumbnails[i], (x, y))
            i += 1
            y += thumbnail_height
        x += thumbnail_width
        y = 0

    extension = os.path.splitext(listofimages[0])[1]
    if extension == "":
        extension = ".jpg"
    destination_file = os.path.join(os.path.dirname(listofimages[0]), f"Collage{extension}")
    new_im.save(destination_file)

Example usage:

listofgraphs = os.listdir("path with all saved graphs")
create_collage(listofimages)

The function finds the two smallest integer multiples of the length of the input list of graphs (e.g. for 12, it returns 3 and 4 rather than 2 and 6) and creates a grid, where the first number is always the smallest of the multiples and it is taken to be the number of columns (i.e. by default the grid gets fewer columns than rows; for 12 images, you get a 4x3 matrix, 4 rows, 3 columns). This it can be customized via the smallest_first argument (only exposed in get_smallest_multiples()).
Optional arguments also allow to force a number of rows/columns.

The final image size is the sum of the sizes of the single images, but an optional thumbnail_scale argument allows to specify a percentage of scaling for all the thumbnails (defaults to 1.0, i.e. 100%, no scaling).

The final image is saved in the same folder where the first of the input images is.

This function works well when the size of the images are all roughly the same. I have not covered more complex scenarios.

alelom
  • 2,130
  • 3
  • 26
  • 38