Reputation: 1271
I am trying to use the gganimate
(R) equivalent, plotnine
(python). I run into problems when one of the plots introduces a new aesthetic scale category, causing the console to throw the error: The fill scale of plot for frame 1 has different limits from those of the first frame.
This appears to be the result of having to produce a generator of all the plots and then stick them together - as per the plotnine documentation (https://plotnine.readthedocs.io/en/stable/generated/plotnine.animation.PlotnineAnimation.html). When I use gganimate()
the library is smart enough to know to check all the scales that will be called and print the complete scale accordingly.
I tried using scale_fill_manual()
to force a scale onto the first plot as a way to deal with this, but it doesn't work (in R or python).
I am looking for a way around this in plotnine (if there is a way better animation module in python i can learn that if there are suggestions).
Here is a worked example:
First trying in plotnine:
#import modules
import pandas as pd
from plotnine import *
from plotnine.animation import PlotnineAnimation
#create the dataframe
df = pd.DataFrame({'run': [1, 1, 1, 2, 2, 2],
'x_pos': [1, 2, 3, 2, 3, 4],
'y_pos': [4, 5, 6, 5, 6, 7],
'status': ['A', 'B', 'A', 'A', 'B', 'C'] })
#write a function that creates all the plots
def plot(x):
df2 = df[df['run'] == x]
p = (ggplot(df2,
aes(x = 'x_pos', y = 'y_pos', fill = 'status'))
+ geom_point()
)
return(p)
#save the plots as a generator as per the plotnine docs (https://plotnine.readthedocs.io/en/stable/generated/plotnine.animation.PlotnineAnimation.html)
plots = (plot(i) for i in range(1, 3))
#create the animation
animation = PlotnineAnimation(plots, interval=100, repeat_delay=500)
Throws the error: PlotnineError: 'The fill scale of plot for frame 1 has different limits from those of the first frame.'
If I do this in R, using gganimate()
, everything works fine - I get all three color scales from the start:
library('ggplot2')
library('gganimate')
df <- data.frame(run = c(1, 1, 1, 2, 2, 2),
x_pos = c(1, 2, 3, 2, 3, 4),
y_pos = c(4, 5, 6, 5, 6, 7),
status = c('A', 'B', 'A', 'A', 'B', 'C'))
ggplot(df, aes(x = x_pos, y = y_pos, col = status)) +
geom_point() +
transition_states(run)
When i try forcing the scale on the first plot it doesn't work. First in R:
library(dplyr)
df %>%
filter(run == 1) %>%
ggplot(aes(x = x_pos, y = y_pos, col = status)) +
geom_point() +
scale_color_manual(labels = c('A', 'B', 'C'),
values = c('red', 'green', 'blue'))
The plot shows with just the two color scales - despite explicitly stating 3 in scale_color_manual()
:
Then in Python:
#Filter the data frame created above
df2 = df[df['run'] == 1]
#Create the plot - stating scale_fill_manual()
p = (ggplot(df2,
aes(x = 'x_pos', y = 'y_pos', fill = 'status'))
+ geom_point()
+ scale_fill_manual(labels = ['A', 'B', 'C'],
values = ['red', 'blue', 'green'])
)
#Print the plot
print(p)
Throws errors about the arrays being the wrong length (ValueError: arrays must all be same length
), which makes sense: I am asking for more values and colors than are in the filtered dataset. That means that I cannot have the first frame match the second frame, which is the error i am trying to solve.
Anyone know how to make this work with plotnine
, the way it does in gganimate()
? Further (though less important), any ideas as to why plotnine
takes fill for geom_point()
, while ggplot2
takes col for geom_point()
?
Upvotes: 0
Views: 311
Reputation: 2375
In python you should make status
a categorical, it is also better to be specific and set the limits of the x
and y
aesthetics, if you leave it up to the plotting system it may get them wrong.
In Plotnine all the key plotting shapes for geom_point
have both the inner area and the stroke around it so you can use fill
and color
to target either. But this is an intricate detail that would most likely frustrate users, so when you map to the color
and not the fill
it sets the same colour for both. In between, Plotnine does not recognise the abbreviation col
.
import pandas as pd
from plotnine import *
from plotnine.animation import PlotnineAnimation
#create the dataframe
df = pd.DataFrame({'run': [1, 1, 1, 2, 2, 2],
'x_pos': [1, 2, 3, 2, 3, 4],
'y_pos': [4, 5, 6, 5, 6, 7],
'status': ['A', 'B', 'A', 'A', 'B', 'C'] })
# A categorical ensures that each of the sub-dataframes
# can be used to create a scale with the correct limits
df['status'] = pd.Categorical(df['status'])
#write a function that creates all the plots
def plot(x):
df2 = df[df['run'] == x]
p = (ggplot(df2,
aes(x = 'x_pos', y = 'y_pos', color = 'status'))
+ geom_point()
# Specify the limits for the x and y aesthetics
+ scale_x_continuous(limits=(df.x_pos.min(), df.x_pos.max()))
+ scale_y_continuous(limits=(df.y_pos.min(), df.y_pos.max()))
+ theme(subplots_adjust={'right': 0.85}) # Make space for the legend
)
return(p)
plots = (plot(i) for i in range(1, 3))
#create the animation
animation = PlotnineAnimation(plots, interval=1000, repeat_delay=500)
animation
Upvotes: 1