<jupyter_start><jupyter_text>❄️ Polar coordinates Building a circular barplot requires using polar coordinates instead of the more usual cartesian coordinates. This kind of representation is accessible through the `polar` parameter of the `subplot()` function of `matplotlib`. Let's initialize a chart that uses polar coordinates:<jupyter_code># Import matplotlib
import matplotlib.pyplot as plt

# Initialize plot with polar coordinates.
plt.subplot(111, polar=True);<jupyter_output><empty_output><jupyter_text>Now let's add a bar to this figure. This is gonna be done through the usual `bar()` function. The same usual parameter must be provided, but they have some different meanings:- `x`: angle of the center of the section we're adding. - `height` and `bottom`: are a bit like the `ylim` values. They control the thickness of the section- `width`: angle of the section<jupyter_code># import numpy to get the value of Pi
import numpy as np

# Add a bar in the polar coordinates
plt.subplot(111, polar=True);
plt.bar(x=0, height=10, width=np.pi/2, bottom=5);<jupyter_output><empty_output><jupyter_text>>Note: angles are provided in radians. Remember that 0 degrees is 0 radians. 180 degrees is Pi radians. And so on.  📍 Data Let's build a basic dataset with 2 columns. The first column provide a name for each item of the dataset. The second column provides a numeric value for the item:<jupyter_code># import pandas for data wrangling
import pandas as pd

# Build a dataset
df = pd.DataFrame(
        {
            'Name': ['item ' + str(i) for i in list(range(1, 51)) ],
            'Value': np.random.randint(low=10, high=100, size=50)
        })

# Show 3 first rows
df.head(3)<jupyter_output><empty_output><jupyter_text>📊 Circular Barplot Let's build a very basic circular barplot from this dataset. Each item will be represented as a bar.<jupyter_code># set figure size
plt.figure(figsize=(20,10))

# plot polar axis
ax = plt.subplot(111, polar=True)

# remove grid
plt.axis('off')

# Set the coordinates limits
upperLimit = 100
lowerLimit = 30

# Compute max and min in the dataset
max = df['Value'].max()

# Let's compute heights: they are a conversion of each item value in those new coordinates
# In our example, 0 in the dataset will be converted to the lowerLimit (10)
# The maximum will be converted to the upperLimit (100)
slope = (max - lowerLimit) / max
heights = slope * df.Value + lowerLimit

# Compute the width of each bar. In total we have 2*Pi = 360°
width = 2*np.pi / len(df.index)

# Compute the angle each bar is centered on:
indexes = list(range(1, len(df.index)+1))
angles = [element * width for element in indexes]
angles

# Draw bars
bars = ax.bar(
    x=angles, 
    height=heights, 
    width=width, 
    bottom=lowerLimit,
    linewidth=2, 
    edgecolor="white")<jupyter_output><empty_output><jupyter_text>🏷️ Add labels The above chart looks not too bad, but is pretty useless without labels. Labels are pretty tricky to add and you have to remember some basic trigonometry concepts in order to get it right.- the `text()` function has a handy `rotation` parameter. This parameter expect degrees, not radians. Fortunately, `numpy` has a handy `rad2deg()` function.- labels must be flipped by 180 on the left side of the figure for readability. It also means that their alignment must be reversed (`ha` parameter)<jupyter_code># initialize the figure
plt.figure(figsize=(20,10))
ax = plt.subplot(111, polar=True)
plt.axis('off')

# Draw bars
bars = ax.bar(
    x=angles, 
    height=heights, 
    width=width, 
    bottom=lowerLimit,
    linewidth=2, 
    edgecolor="white",
    color="#61a4b2",
)

# little space between the bar and the label
labelPadding = 4

# Add labels
for bar, angle, height, label in zip(bars,angles, heights, df["Name"]):

    # Labels are rotated. Rotation must be specified in degrees :(
    rotation = np.rad2deg(angle)

    # Flip some labels upside down
    alignment = ""
    if angle >= np.pi/2 and angle < 3*np.pi/2:
        alignment = "right"
        rotation = rotation + 180
    else: 
        alignment = "left"

    # Finally add the labels
    ax.text(
        x=angle, 
        y=lowerLimit + bar.get_height() + labelPadding, 
        s=label, 
        ha=alignment, 
        va='center', 
        rotation=rotation, 
        rotation_mode="anchor")<jupyter_output><empty_output><jupyter_text>↕️ Reordering Last but not least, a bar chart is always [more insightful when ordered](https://www.data-to-viz.com/caveat/order_data.html). It allows to understand each item rank much quicker. To do so, you simply have to reorder the initial dataframe with the `sort_values()` function and run the same code. Let's check the complete code:<jupyter_code># import pandas for data wrangling
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

# Build a dataset
df = pd.DataFrame(
        {
            'Name': ['item ' + str(i) for i in list(range(1, 51)) ],
            'Value': np.random.randint(low=10, high=100, size=50)
        })

# Reorder the dataframe
df = df.sort_values(by=['Value'])

# initialize the figure
plt.figure(figsize=(20,10))
ax = plt.subplot(111, polar=True)
plt.axis('off')

# Constants = parameters controling the plot layout:
upperLimit = 100
lowerLimit = 30
labelPadding = 4

# Compute max and min in the dataset
max = df['Value'].max()

# Let's compute heights: they are a conversion of each item value in those new coordinates
# In our example, 0 in the dataset will be converted to the lowerLimit (10)
# The maximum will be converted to the upperLimit (100)
slope = (max - lowerLimit) / max
heights = slope * df.Value + lowerLimit

# Compute the width of each bar. In total we have 2*Pi = 360°
width = 2*np.pi / len(df.index)

# Compute the angle each bar is centered on:
indexes = list(range(1, len(df.index)+1))
angles = [element * width for element in indexes]
angles

# Draw bars
bars = ax.bar(
    x=angles, 
    height=heights, 
    width=width, 
    bottom=lowerLimit,
    linewidth=2, 
    edgecolor="white",
    color="#61a4b2",
)

# Add labels
for bar, angle, height, label in zip(bars,angles, heights, df["Name"]):

    # Labels are rotated. Rotation must be specified in degrees :(
    rotation = np.rad2deg(angle)

    # Flip some labels upside down
    alignment = ""
    if angle >= np.pi/2 and angle < 3*np.pi/2:
        alignment = "right"
        rotation = rotation + 180
    else: 
        alignment = "left"

    # Finally add the labels
    ax.text(
        x=angle, 
        y=lowerLimit + bar.get_height() + labelPadding, 
        s=label, 
        ha=alignment, 
        va='center', 
        rotation=rotation, 
        rotation_mode="anchor")<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09961286480047647, 'fraction_numerical': 0.01891006551518761, 'mean_word_length': 3.6292212267401793, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 28, 'https://': 1, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '307771', 'license': '0BSD', 'n_tokens_mistral': 2189, 'n_tokens_neox': 2016, 'n_words': 797, 'path': 'src/notebooks/circular-barplot-basic.ipynb', 'repo_name': 's-lasch/The-Python-Graph-Gallery', 'size': '6716'}