pysal / splot

Lightweight plotting for geospatial analysis in PySAL
BSD 3-Clause "New" or "Revised" License
98 stars 26 forks source link

Radar chart feature for visualizing geodemographic classifications #49

Open SamComber opened 5 years ago

SamComber commented 5 years ago

Having recently worked on a paper that visualizes the findings of a geodemographic classification (w/ @darribas), we were wondering if there might be appetite for introducing a radar chart to the plotting library for displaying multivariate variation across the resulting cluster partitions.

AFAIK there is no quick and easy mapping library offering this functionality, so if there is interest in introducing this feature I would be happy to contribute the code I have been using for the working paper.

knaaptime commented 5 years ago

it would definitely be of interest to osnap, but might be better implemented here. Do you have other ideas about useful visualizations for geodemographic-style work? We have @slumnitz joining us for the next few months, so it would be great to capture ideas for other viz we could build into splot that support these analyses

darribas commented 5 years ago

My vote would probably go to have it here and then use it as needed in osnap, as splot is designed to support a more general range of applications. Having said that, osnap is focused on neighborhoods so, to the extent radar charts are mostly used in that context, that might also make sense.

@slumnitz, do you think this could be contributed to splot in a way that is more general than "just" for neighborhood analysis? I'd think so, as arguably in some regionalisations this would be useful too?

knaaptime commented 5 years ago

sounds like we're of one mind... if it's implemented here, it would be available to a wider set of use cases and osnap could just consume it. osnap might be the primary consumer for awhile until we figure out how broadly applicable this particular viz is, but that seems like the strategy i'd vote for too

slumnitz commented 5 years ago

Yeah I think all your suggestions make sense! We could create a function api that allows quite generic input here in splot under a specific splot.? namespace (splot.cluster, splot.point, splot.utils, splot.model ... we can have a think about where else radar plots would be helpful) and then additionally expose the functionality as .plot() functionality tailored to osnap in osnap. This would only add a splot dependency to osnap if that is ok with @knaaptime?

@SamComber code contributions are always super helpful and welcome! :) We could also think about exposing you project with the splot radar plot as a notebook if you are interested?

slumnitz commented 4 years ago

Hi @SamComber would you still be interested in integrating this feature into splot? I am happy to help. Do you have a link to the code to have a look? Also do you have an example viz, that might also help in deciding where this should go. Hope all is well!

SamComber commented 4 years ago

Hi @slumnitz, unfortunately I'm quite tied up finishing off my PhD and internship (concurrently) at the moment, so my time is quite limited for the next two months or so. Here's a code snippet that creates a similar visualisation to what I used in the paper, albeit with some toy data. The code is pretty simple, so I'll paste it below, but I guess a lot more work would be needed to make it robust!

I think this would be a really useful feature for summarising geodemographics or even clustering exercises in general, as I struggled to find anything for creating these kind of charts.

import math
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

def spider_plot(row, title, color, ax=None):

    categories=list(radar_dfs)
    N = len(categories)

    # generate slices 
    angles = np.arange(N+1)/N*2*np.pi
    values = radar_dfs.iloc[row].values.flatten().tolist()
    values += values[:1]

    # plot radar surface
    ax.plot(angles, values, color=color,linewidth=2, linestyle='solid')
    ax.fill(angles, values, color=color, alpha=0.4)

    # change position of number legend
#     ax.set_theta_offset(math.pi / 2)
#     ax.set_theta_direction(-1)

    # change label and tick params
    ax.set_xticks(angles[:-1])
    ax.set_xticklabels(categories, color='grey', size=16)
    ax.tick_params(labelleft=True, pad=10)

    ax.set_title(title, pad=20, color=color, size=24)

radar_dfs = pd.DataFrame.from_dict({"v1":{"0":-0.3470532925,"1":-0.082144001,"2":-0.082144001,"3":-0.3470532925,"4":-0.3470532925},
                                    "v2":{"0":-0.1858487321,"1":-0.1685491141,"2":-0.1632483955,"3":-0.1769700284,"4":-0.0389887094},
                                    "v3":{"0":-0.073703681,"1":-0.073703681,"2":-0.073703681,"3":-0.073703681,"4":-0.073703681},
                                    "v4":{"0":-0.2416123064,"1":-0.2841806825,"2":-0.259622004,"3":-0.3529449824,"4":-0.3414842657},
                                    "v5":{"0":-0.1271390651,"1":-0.3105853643,"2":-0.2316607937,"3":-0.3297832328,"4":-0.4599021194},
                                    "v6":{"0":-0.1662745006,"1":-0.1426329043,"2":-0.1577528867,"3":-0.163560133,"4":-0.1099718326},
                                    "v7":{"0":-0.0251535462,"1":-0.1540641646,"2":-0.0204666924,"3":-0.0515740013,"4":-0.0445135996},
                                    "v8":{"0":-0.0826103951,"1":-0.1777759951,"2":-0.114263357,"3":-0.1787044751,"4":-0.2709496389},
                                    "v9":{"0":-0.105481688,"1":-0.1760349683,"2":-0.128215043,"3":-0.1560577648,"4":-0.1760349683}})

palette =['#79BD9A','#69D2E7','#F38630', '#547980','#EDC951']
labels = ['A', 'B', 'C', 'D', 'E']

# 2x1x2 layout
fig = plt.figure(figsize=(20,16))
position = [[0,0], [2,0], [1,1], [0,2], [2,2]]

axes = []
for row, (letter, col) in enumerate(zip(labels, palette)):
    ax = plt.subplot2grid([4,3], position[row], rowspan=2, colspan=1, **{'polar': True}, sharey=axes[0] if row else None)
    axes.append(ax)
    spider_plot( row  = row, title='Group ' + str(letter), color=col, ax=ax)

plt.subplots_adjust(wspace=.4, hspace=.3)
plt.show()

example_viz_clusters