Stars Charts in Python. A Tutorial to Create Star Charts using... | by Cody Glickman, PhD | Towards Data Science

Diamonds are a data scientist’s best friend. More specifically, the diamond dataset found on Kaggle. In this article, I will walk through a simple workflow to create a Star Chart (aka Spider Chart or Radar Charts). This tutorial was adapted from the wonderful workflow of Alex at Python Charts. All of the code in this article and the needed dataset are available on GitHub.
To begin you will need a few libraries. I am running Python 3. I created this workflow using a Jupyter notebook, pandas, matplotlib, and numpy. These packages can be installed via pip or conda if they are not already on your system.
pip install jupyterlabpip install pandaspip install matplotlib pip install numpy
The dataset can be downloaded from Kaggle and should be around 3.2 MB. I have included a copy of the dataset on the Github. I have the dataset in the data folder. Load the dataset with pandas, drop the extra index column, and we are off!
df = pd.read_csv("data/diamonds.csv")df.drop("Unnamed: 0", axis=1, inplace=True)
The 4 C’s of diamonds are Cut, Color, Clarity, and Carat. Cut, Color, and Clarity are defined as categorical variables used in the diamond industry. Carat is a numeric representing the weight of a stone.
To create the Star chart, we need to represent the diamond industry terms as numerics. To do this we need to gather information about the levels that are in our dataset. Cut is composed five levels with Ideal being the highest [4] and Fair being the lowest level [0]. In the seven levels of Color, D is the highest [6] and J is the lowest level [0]. Finally, Clarity is composed of eight levels with IF, meaning internally flawless as the highest level [7] and I1, inclusions level 1, as the lowest level [0].
In our dataset, we cut 3 outliers in carat size that skew the downstream column scaling.
## Cut diamonds that skew carat rangeindicies_to_remove = [27415, 27630, 27130]df = df.drop(indicies_to_remove)
Next, we create new columns in our dataframe to house the rankings created by mapping a dictionary against our C’s column. An example of the mapping is below.
cut={'Ideal':4,'Premium':3,'Very Good':2,'Good': 1,'Fair':0}df['Cut'] = df['cut'].map(cut) #Note: 'Cut' is a different column
Finally, we need to scale the columns that we will use in our Star Chart to represent the data fairly.
## Convert all rankings and contiguous data to scale between 0-100factors = ['Cut', 'Color', "Clarity", "carat", "price"]new_max = 100new_min = 0new_range = new_max - new_min## Create Scaled Columnsfor factor in factors:    max_val = df[factor].max()    min_val = df[factor].min()    val_range = max_val - min_val    df[factor + '_Adj'] = df[factor].apply(lambda x: (((x - min_val) * new_range) / val_range) + new_min)
We then subset the scaled columns for downstream plotting. Notice how we are creating a new dataframe (df2) with only the columns we intend to use in the Star Chart.
## Subset scaled columns df2 = df[['Cut_Adj', "Color_Adj", "Clarity_Adj", "carat_Adj", "price_Adj"]]df2.columns = ['Cut', "Color", "Clarity", "Carat", "Price"]
To create the Star Chart, we must specify which columns to use and create the circular plot object using numpy.
labels = ['Cut', "Color", "Clarity", "Carat", "Price"]points = len(labels)angles = np.linspace(0, 2 * np.pi, points, endpoint=False).tolist()angles += angles[:1]
We then create a helper function to plot a diamond solely by the index number.
def add_to_star(diamond, color, label=None):    values = df2.loc[diamond].tolist()    values += values[:1]    if label != None:        ax.plot(angles, values, color=color, linewidth=1, label=label)    else:        ax.plot(angles, values, color=color, linewidth=1, label=diamond)    ax.fill(angles, values, color=color, alpha=0.25)
Now the magic begins! We can begin populating our Star Chart with any diamonds we want. How about the most expensive and the two least expensive:
## Create plot object   fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(polar=True))## Plot a new diamond with the add_to_star functionadd_to_star(27749, '#1aaf6c', "Most Expensive Diamond")add_to_star(0, '#429bf4', "Least Expensive A")add_to_star(1, '#d42cea', "Least Expensive B")
This amount is enough to create a Star Chart, however, there are no x labels, no orientation, and no custom flair. Let’s change that!
## Fix axis to star from topax.set_theta_offset(np.pi / 2)ax.set_theta_direction(-1)## Edit x axis labelsfor label, angle in zip(ax.get_xticklabels(), angles):    if angle in (0, np.pi):        label.set_horizontalalignment('center')    elif 0 < angle < np.pi:        label.set_horizontalalignment('left')    else:        label.set_horizontalalignment('right')## Customize your graphic# Change the location of the gridlines or remove themax.set_rgrids([20, 40, 60 ,80])#ax.set_rgrids([]) # This removes grid lines# Change the color of the ticksax.tick_params(colors='#222222')# Make the y-axis labels larger, smaller, or remove by setting fontsizeax.tick_params(axis='y', labelsize=0)# Make the x-axis labels larger or smaller.ax.tick_params(axis='x', labelsize=13)# Change the color of the circular gridlines.ax.grid(color='#AAAAAA')# Change the color of the outer circleax.spines['polar'].set_color('#222222')# Change the circle background colorax.set_facecolor('#FAFAFA')# Add title and legendax.set_title('Comparing Diamonds Across Dimensions', y=1.08)ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1.1))# Draw axis lines for each angle and label.ax.set_thetagrids(np.degrees(angles), labels)
So what is the output?
What is the diamond with the highest rating across the 4 C’s with the lowest price? To find out we must get the total value across the 4 C’s and divide by the raw (unscaled) price. This section operates over the original dataframe with all the raw columns. To find the total, we sum the four scaled columns.
df['Total'] = df['Cut_Adj'] + df['Color_Adj'] + df['Clarity_Adj'] + df['carat_Adj']## Divide Value total by Pricedf['4C_by_Price'] = df['Total']/df['price']df = df.sort_values(by="4C_by_Price", ascending=False)
The diamond with the most bling for our buck is #31597 and the diamond with the least bling for our buck is #26320. How do these diamonds compare on a Star Chart? Let’s explore below:
Thank you for exploring a few diamond characteristics in a Star Chart format using matplotlib. If you have any questions post them below or to the location of the full code, the GitHub repository. My name is Cody Glickman and I can be found on LinkedIn. Be sure to check out some other articles about fun data science projects!