Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add circular histogram and KDE plot #1266

Merged
merged 26 commits into from
Jul 14, 2020
Merged

Add circular histogram and KDE plot #1266

merged 26 commits into from
Jul 14, 2020

Conversation

agustinaarroyuelo
Copy link
Contributor

@agustinaarroyuelo agustinaarroyuelo commented Jun 25, 2020

Description

This PR adds circular histogram and circular KDE plot.
Histogram example (matplotlib):
kde_radians_degrees

Histogram example (bokeh):
bokeh_circ_hist

Although with this code you can get a circular KDE, there are a couple of things to improve in the KDE that will result in better behavior in a circular setting. We are planning to follow on Tomas Capretto's work on KDE. You can read the details in his report.

Checklist

  • Follows official PR format
  • Includes a sample plot to visually illustrate the changes (only for plot-related functions)
  • New features are properly documented (with an example if appropriate)?
  • Includes new or updated tests to cover the new feature
  • Code style correct (follows pylint and black guidelines)
  • Changes are listed in changelog

arviz/plots/backends/matplotlib/distplot.py Outdated Show resolved Hide resolved
arviz/plots/backends/matplotlib/distplot.py Outdated Show resolved Hide resolved
arviz/plots/backends/matplotlib/kdeplot.py Outdated Show resolved Hide resolved
arviz/plots/backends/bokeh/distplot.py Outdated Show resolved Hide resolved
arviz/plots/backends/bokeh/distplot.py Outdated Show resolved Hide resolved
arviz/plots/backends/bokeh/distplot.py Outdated Show resolved Hide resolved
arviz/plots/backends/bokeh/distplot.py Outdated Show resolved Hide resolved
arviz/plots/backends/bokeh/distplot.py Outdated Show resolved Hide resolved
**hist_kwargs,
)

ticks = np.linspace(-np.pi, np.pi, 9)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be

ticks = np.linspace(-np.pi, np.pi, len(labels), endpoint=False)

y=0,
inner_radius=0,
outer_radius=np.max(outer_radius) * 1.1,
start_angle=ticks[:-1],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and this could just be start_angle=ticks

inner_radius=0,
outer_radius=np.max(outer_radius) * 1.1,
start_angle=ticks[:-1],
end_angle=ticks[:-1] + 0.002,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need the 0.002 or could we just use end_angle=ticks?

@agustinaarroyuelo
Copy link
Contributor Author

Thanks @aloctavodia for your comments!

@aloctavodia
Copy link
Contributor

Nitpicking: The grey radial lines indicating the angles are of the same color as the wedge's borders. We may want to 1)remove the borders (make them the same color as the wedges), 2) make the border black, 3) add a small blank space between wedges. Probably the first option is easier and it will work well. But the last one will be more consistent with the matplotlib histogram (circular and not)

@agustinaarroyuelo agustinaarroyuelo changed the title [WIP] Add circular histogram and KDE plot Add circular histogram and KDE plot Jul 1, 2020
fix docstring

fix changelog
Copy link
Member

@OriolAbril OriolAbril left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

I have a couple of high level questions too, the comments are minor nits.

Do both degrees and radians units work with bokeh hist, mpl hist and mpl kde? Do you think it would be worth it to create a function in plot_utils that got is_circular and returned the corresponding ticks and ticklabels?

I understand all polar plots will show the complete circle, do you plan to support other angular ranges? (again, I know close to nothing on circular variables so I am not even sure it would be worth to support half circle or something like this)

CHANGELOG.md Outdated Show resolved Hide resolved
CHANGELOG.md Outdated Show resolved Hide resolved
arviz/plots/backends/bokeh/distplot.py Outdated Show resolved Hide resolved
arviz/plots/backends/bokeh/energyplot.py Outdated Show resolved Hide resolved
Comment on lines 46 to +47
if ax is None:
ax = plt.gca()
ax = plt.gca(polar=is_circular)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is using gca instead of a new axes intended or a forgotten TODO from when bokeh was introduced? cc @ahartikainen @canyon289 @aloctavodia

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is intended

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will gca grab possibly old axis here? Have we created a new figure somewhere?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if present, plt.gca will grab old axes yes

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is considered a "best practice" with matplotlib, and a pattern they endorse.

arviz/plots/distplot.py Outdated Show resolved Hide resolved
arviz/plots/distplot.py Outdated Show resolved Hide resolved
arviz/plots/backends/matplotlib/distplot.py Outdated Show resolved Hide resolved
arviz/plots/distplot.py Outdated Show resolved Hide resolved
@codecov
Copy link

codecov bot commented Jul 1, 2020

Codecov Report

Merging #1266 into master will increase coverage by 0.09%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1266      +/-   ##
==========================================
+ Coverage   93.02%   93.12%   +0.09%     
==========================================
  Files         101      101              
  Lines        9908    10034     +126     
==========================================
+ Hits         9217     9344     +127     
+ Misses        691      690       -1     
Impacted Files Coverage Δ
arviz/plots/backends/bokeh/energyplot.py 100.00% <ø> (ø)
arviz/plots/distplot.py 93.33% <ø> (-0.42%) ⬇️
arviz/plots/backends/bokeh/distplot.py 87.87% <100.00%> (+2.69%) ⬆️
arviz/plots/backends/matplotlib/distplot.py 89.58% <100.00%> (+6.25%) ⬆️
arviz/plots/backends/matplotlib/kdeplot.py 97.46% <100.00%> (+0.20%) ⬆️
arviz/plots/kdeplot.py 100.00% <100.00%> (ø)
arviz/plots/plot_utils.py 94.40% <100.00%> (+0.20%) ⬆️
arviz/data/io_cmdstanpy.py 100.00% <0.00%> (ø)
arviz/plots/backends/matplotlib/forestplot.py 97.93% <0.00%> (+<0.01%) ⬆️
arviz/stats/stats_utils.py 95.66% <0.00%> (+0.01%) ⬆️
... and 9 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 1ef0dd5...583079c. Read the comment docs.

@agustinaarroyuelo
Copy link
Contributor Author

Thanks for your comments @OriolAbril.

Yes, both degrees and radians will work. Creating a function to manage ticks and ticklabels can be specially useful for the circular plots in Bokeh.

I am almost sure that you can get an angular range plot by working on the axes returned by the Matplotlib function. Not quite sure about Bokeh. Since it does not support polar projection, things can get a bit tricky.

arviz/plots/distplot.py Outdated Show resolved Hide resolved
Comment on lines 46 to +47
if ax is None:
ax = plt.gca()
ax = plt.gca(polar=is_circular)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is intended

arviz/plots/backends/bokeh/distplot.py Outdated Show resolved Hide resolved

if is_circular == "degrees":
edges = np.deg2rad(edges)
labels = ["0°", "45°", "90°", "135°", "180°", "225°", "270°", "315°"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are always showing a full circle plot?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's what everybody does (I know this is not a very good argument, haha)

Comment on lines 46 to +47
if ax is None:
ax = plt.gca()
ax = plt.gca(polar=is_circular)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will gca grab possibly old axis here? Have we created a new figure somewhere?

@aloctavodia
Copy link
Contributor

I think this is ready to merge, right? @ahartikainen @OriolAbril what do you think?

Copy link
Member

@ColCarroll ColCarroll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is really clean code!

Comment on lines 46 to +47
if ax is None:
ax = plt.gca()
ax = plt.gca(polar=is_circular)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is considered a "best practice" with matplotlib, and a pattern they endorse.

@ColCarroll
Copy link
Member

Also, does this make it easy to recreate MPL's logo?
image

Comment on lines 130 to 131
if rotated or is_circular:
ax.set_yticks(bins[:-1])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In polar plots, isn't the y axis the radial one? could this be setting the radial ticks with x values?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for spotting this!

arviz/plots/backends/matplotlib/distplot.py Outdated Show resolved Hide resolved
arviz/plots/kdeplot.py Outdated Show resolved Hide resolved
Comment on lines 270 to 271
# Skip tests if bokeh not installed
bkp = importorskip("bokeh.plotting") # pylint: disable=invalid-name
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will skip all tests in the file if bokeh is not installed. Maybe we should create a test_plot_utils_bokeh? similarly to what is done with test_stats and test_utils?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used skipif, but I am not sure about the import statement placement. Let me know what you think

Comment on lines 101 to 102
else:
ax.set_xticks(bins[:-1])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should not be removed as it will modify the behaviour of the plot, if it has to be skipped for circular plots I'd suggest a elif not is_circular

arviz/tests/base_tests/test_plot_utils.py Outdated Show resolved Hide resolved
agustinaarroyuelo and others added 2 commits July 11, 2020 15:00
Comment on lines 382 to 389
{"is_circular": False},
{"is_circular": True},
{"is_circular": "radians"},
{"is_circular": "degrees"},
],
)
def test_plot_dist(continuous_model, kwargs):
axes = plot_dist(continuous_model["x"], **kwargs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was quite shocked by the low coverage given that there are tests for everything, I was expecting something close to 100% diff coverage. I think the issue (and same happens with bokeh) is that the variables in the model are continuous, so the kde is plotted. I am not sure if it would be better to add a new test of divide the fixture into two so all combinations are tested

@OriolAbril
Copy link
Member

Nice! Now everything checks out, thanks! I think it's ready to merge

@aloctavodia aloctavodia merged commit 04201c5 into arviz-devs:master Jul 14, 2020
@agustinaarroyuelo agustinaarroyuelo deleted the circular_histogram branch July 14, 2020 17:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants