Skip to content

Polar Plots

Polar plots display data in circular coordinates (angle and radius), useful for directional data, periodic phenomena, and radar charts.

Mental Model

A polar plot replaces the x-y grid with an angle-radius grid: the angle goes around the circle (in radians) and the radius goes outward from the center. Create polar axes with projection='polar' and then use the same ax.plot() and ax.fill() calls — only the coordinate interpretation changes.

When to Use Polar (and When Not To)

Use polar coordinates when the variable is inherently cyclic — wind direction, time of day, phase angles, or any quantity that wraps around. If the data is not cyclic, a polar plot distorts distances and makes comparison harder than a Cartesian plot. Do not use polar just because it looks interesting — use it because the circular structure reveals a pattern that Cartesian coordinates would hide.

Creating Polar Axes

Method 1: subplot with projection

```python import matplotlib.pyplot as plt import numpy as np

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})

theta = np.linspace(0, 2 * np.pi, 100) r = 1 + np.sin(theta)

ax.plot(theta, r) plt.show() ```

Method 2: add_subplot with polar=True

python fig = plt.figure() ax = fig.add_subplot(111, polar=True) ax.plot(theta, r) plt.show()

Method 3: plt.polar (pyplot interface)

python plt.polar(theta, r) plt.show()

Basic Polar Line Plot

```python import matplotlib.pyplot as plt import numpy as np

theta = np.linspace(0, 2 * np.pi, 100) r = np.abs(np.sin(2 * theta) * np.cos(2 * theta))

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}) ax.plot(theta, r, 'b-', linewidth=2) ax.set_title('Rose Curve') plt.show() ```

Common Polar Curves

Cardioid

python fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}) theta = np.linspace(0, 2 * np.pi, 100) r = 1 + np.cos(theta) # Cardioid: r = 1 + cos(θ) ax.plot(theta, r) ax.set_title('Cardioid') plt.show()

Rose Curves

```python fig, axes = plt.subplots(1, 3, subplot_kw={'projection': 'polar'}, figsize=(12, 4))

theta = np.linspace(0, 2 * np.pi, 1000)

n=2: 4 petals

axes[0].plot(theta, np.sin(2 * theta)) axes[0].set_title('r = sin(2θ)')

n=3: 3 petals

axes[1].plot(theta, np.sin(3 * theta)) axes[1].set_title('r = sin(3θ)')

n=5: 5 petals

axes[2].plot(theta, np.sin(5 * theta)) axes[2].set_title('r = sin(5θ)')

plt.tight_layout() plt.show() ```

Spiral

```python fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}) theta = np.linspace(0, 6 * np.pi, 500) r = theta # Archimedean spiral

ax.plot(theta, r) ax.set_title('Archimedean Spiral') plt.show() ```

Polar Scatter Plot

```python import matplotlib.pyplot as plt import numpy as np

np.random.seed(42) n = 100 theta = 2 * np.pi * np.random.rand(n) r = np.random.rand(n) colors = np.random.rand(n) sizes = 100 * np.random.rand(n)

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}) scatter = ax.scatter(theta, r, c=colors, s=sizes, cmap='viridis', alpha=0.7) plt.colorbar(scatter) plt.show() ```

Polar Bar Chart (Radar/Spider Chart)

Basic Radar Chart

```python import matplotlib.pyplot as plt import numpy as np

categories = ['Speed', 'Reliability', 'Comfort', 'Safety', 'Efficiency'] n_cats = len(categories)

Values for two products

values1 = [4, 3, 5, 4, 3] values2 = [3, 5, 3, 5, 4]

Compute angle for each category

angles = np.linspace(0, 2 * np.pi, n_cats, endpoint=False).tolist()

Close the plot

values1 += values1[:1] values2 += values2[:1] angles += angles[:1]

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})

ax.plot(angles, values1, 'o-', linewidth=2, label='Product A') ax.fill(angles, values1, alpha=0.25)

ax.plot(angles, values2, 'o-', linewidth=2, label='Product B') ax.fill(angles, values2, alpha=0.25)

ax.set_xticks(angles[:-1]) ax.set_xticklabels(categories) ax.set_ylim(0, 5) ax.legend(loc='upper right') ax.set_title('Product Comparison') plt.show() ```

Radar Chart Function

```python def radar_chart(categories, values_dict, title='Radar Chart'): """ Create radar chart for multiple series.

Parameters:
- categories: list of category names
- values_dict: dict mapping series names to value lists
- title: chart title
"""
n_cats = len(categories)
angles = np.linspace(0, 2 * np.pi, n_cats, endpoint=False).tolist()
angles += angles[:1]  # Close the plot

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(8, 8))

for name, values in values_dict.items():
    vals = values + values[:1]  # Close the plot
    ax.plot(angles, vals, 'o-', linewidth=2, label=name)
    ax.fill(angles, vals, alpha=0.2)

ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories)
ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1.0))
ax.set_title(title)

return fig, ax

Usage

categories = ['A', 'B', 'C', 'D', 'E'] values_dict = { 'Series 1': [4, 3, 5, 2, 4], 'Series 2': [3, 4, 3, 4, 5], 'Series 3': [5, 2, 4, 3, 3] } radar_chart(categories, values_dict) plt.show() ```

Polar Fill

Fill Between

```python fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})

theta = np.linspace(0, 2 * np.pi, 100) r1 = 1 + 0.5 * np.sin(3 * theta) r2 = 2 + 0.5 * np.cos(3 * theta)

ax.fill_between(theta, r1, r2, alpha=0.3) ax.plot(theta, r1, 'b-') ax.plot(theta, r2, 'r-') plt.show() ```

Filled Area

```python fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}) theta = np.linspace(0, 2 * np.pi, 100) r = 1 + np.sin(theta)

ax.fill(theta, r, alpha=0.5, color='blue') ax.plot(theta, r, 'b-', linewidth=2) plt.show() ```

Customizing Polar Axes

Angular Ticks (Theta)

```python fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}) ax.plot(theta, r)

Set theta tick locations (in radians)

ax.set_xticks(np.linspace(0, 2*np.pi, 8, endpoint=False))

Set theta tick labels

ax.set_xticklabels(['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'])

plt.show() ```

Radial Ticks

```python fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}) ax.plot(theta, r)

Set radial limits

ax.set_rlim(0, 2)

Set radial ticks

ax.set_rticks([0.5, 1, 1.5, 2])

plt.show() ```

Theta Direction and Zero Position

```python fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}) ax.plot(theta, r)

Set zero position (default is 'E' = right)

ax.set_theta_zero_location('N') # 0 degrees at top

Set direction (default is counter-clockwise)

ax.set_theta_direction(-1) # Clockwise

plt.show() ```

Grid Styling

```python fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}) ax.plot(theta, r)

ax.grid(True, linestyle='--', alpha=0.7) ax.set_facecolor('lightyellow')

plt.show() ```

Practical Examples

1. Wind Rose Diagram

```python import matplotlib.pyplot as plt import numpy as np

Wind direction data (degrees)

directions = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'] n_dirs = len(directions) angles = np.linspace(0, 2*np.pi, n_dirs, endpoint=False)

Wind frequency by direction and speed category

speeds = { '0-5 m/s': [10, 5, 8, 12, 15, 10, 7, 8], '5-10 m/s': [5, 8, 12, 8, 10, 7, 5, 6], '10+ m/s': [2, 3, 5, 3, 4, 2, 1, 2] }

width = 2*np.pi / n_dirs * 0.8 # Bar width

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(10, 10))

bottom = np.zeros(n_dirs) for speed, values in speeds.items(): bars = ax.bar(angles, values, width=width, bottom=bottom, label=speed, alpha=0.7) bottom += values

ax.set_theta_zero_location('N') ax.set_theta_direction(-1) ax.set_xticks(angles) ax.set_xticklabels(directions) ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1.0)) ax.set_title('Wind Rose Diagram') plt.show() ```

2. Daily Activity Pattern

```python import matplotlib.pyplot as plt import numpy as np

hours = np.arange(0, 24) angles = hours * 2 * np.pi / 24

Activity levels throughout the day

activity = [2, 1, 1, 1, 1, 2, 4, 6, 8, 9, 8, 7, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 3, 2]

Close the loop

activity = activity + [activity[0]] angles = np.append(angles, angles[0])

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})

ax.plot(angles, activity, 'b-', linewidth=2) ax.fill(angles, activity, alpha=0.3)

ax.set_theta_zero_location('N') ax.set_theta_direction(-1) ax.set_xticks(np.linspace(0, 2*np.pi, 24, endpoint=False)) ax.set_xticklabels([f'{h:02d}:00' for h in range(24)]) ax.set_title('Daily Activity Pattern') plt.show() ```

3. Antenna Radiation Pattern

```python import matplotlib.pyplot as plt import numpy as np

theta = np.linspace(0, 2*np.pi, 360)

Dipole antenna pattern (simplified)

r_dipole = np.abs(np.sin(theta))

Directional antenna pattern

r_directional = np.abs(np.cos(theta))**2

fig, (ax1, ax2) = plt.subplots(1, 2, subplot_kw={'projection': 'polar'}, figsize=(12, 5))

ax1.plot(theta, r_dipole) ax1.fill(theta, r_dipole, alpha=0.3) ax1.set_title('Dipole Pattern')

ax2.plot(theta, r_directional) ax2.fill(theta, r_directional, alpha=0.3) ax2.set_title('Directional Pattern')

plt.tight_layout() plt.show() ```

4. Sector Performance

```python import matplotlib.pyplot as plt import numpy as np

sectors = ['Tech', 'Finance', 'Healthcare', 'Energy', 'Consumer', 'Industrial', 'Materials', 'Utilities'] n_sectors = len(sectors) angles = np.linspace(0, 2*np.pi, n_sectors, endpoint=False)

Performance metrics

returns = [15, 8, 12, -5, 7, 10, 3, 5] # %

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(10, 10))

Normalize for visualization (shift to positive)

r = np.array(returns) + 10

bars = ax.bar(angles, r, width=0.6, alpha=0.7, color=['green' if ret > 0 else 'red' for ret in returns])

ax.set_xticks(angles) ax.set_xticklabels(sectors) ax.set_ylim(0, 30) ax.set_rticks([5, 10, 15, 20, 25]) ax.set_yticklabels(['-5%', '0%', '5%', '10%', '15%']) ax.set_title('Sector Performance') plt.show() ```

Multiple Polar Subplots

```python fig, axes = plt.subplots(2, 2, subplot_kw={'projection': 'polar'}, figsize=(10, 10))

theta = np.linspace(0, 2*np.pi, 100)

curves = [ ('Cardioid', 1 + np.cos(theta)), ('Rose (n=3)', np.sin(3theta)), ('Spiral', theta/10), ('Lemniscate', np.sqrt(np.abs(np.cos(2theta)))) ]

for ax, (name, r) in zip(axes.flat, curves): ax.plot(theta, r) ax.set_title(name)

plt.tight_layout() plt.show() ```

Key Methods for Polar Axes

Method Description
set_theta_zero_location(loc) Position of θ=0 ('N', 'E', 'S', 'W')
set_theta_direction(direction) 1=counterclockwise, -1=clockwise
set_rlim(min, max) Set radial limits
set_rticks(ticks) Set radial tick positions
set_xticks(ticks) Set angular tick positions
set_xticklabels(labels) Set angular tick labels
set_rgrids(radii) Set radial grid lines
set_thetagrids(angles) Set angular grid lines

Exercises

Exercise 1. Create a polar plot of a four-petaled rose curve defined by \(r = \cos(2\theta)\) for \(\theta \in [0, 2\pi]\). Fill the curve with a semi-transparent color using fill and add a descriptive title.

Solution to Exercise 1
import matplotlib.pyplot as plt
import numpy as np

theta = np.linspace(0, 2 * np.pi, 1000)
r = np.cos(2 * theta)

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(6, 6))
ax.plot(theta, r, color='purple')
ax.fill(theta, r, alpha=0.3, color='purple')
ax.set_title('Four-Petaled Rose: r = cos(2θ)', pad=20)
plt.show()

Exercise 2. Build a radar chart (spider plot) for comparing three students across five subjects: Math, Science, English, History, and Art. Use scores like [85, 90, 78, 92, 88], [70, 85, 95, 80, 75], and [90, 75, 80, 85, 95]. Close each polygon by repeating the first value. Add a legend.

Solution to Exercise 2
import matplotlib.pyplot as plt
import numpy as np

categories = ['Math', 'Science', 'English', 'History', 'Art']
n = len(categories)
angles = np.linspace(0, 2 * np.pi, n, endpoint=False).tolist()
angles += angles[:1]

students = {
    'Alice': [85, 90, 78, 92, 88],
    'Bob':   [70, 85, 95, 80, 75],
    'Carol': [90, 75, 80, 85, 95],
}

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(7, 7))

for name, scores in students.items():
    values = scores + scores[:1]
    ax.plot(angles, values, 'o-', label=name)
    ax.fill(angles, values, alpha=0.1)

ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories)
ax.set_ylim(0, 100)
ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1.1))
ax.set_title('Student Performance Radar Chart', pad=20)
plt.show()

Exercise 3. Plot the Archimedean spiral \(r = \theta\) for \(\theta \in [0, 6\pi]\) in polar coordinates. Color the line by the angle value using a colormap by plotting small segments with plt.scatter in polar mode. Add a colorbar labeled "Angle (radians)".

Solution to Exercise 3
import matplotlib.pyplot as plt
import numpy as np

theta = np.linspace(0, 6 * np.pi, 1000)
r = theta

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(7, 7))
sc = ax.scatter(theta, r, c=theta, cmap='viridis', s=2)
ax.set_title('Archimedean Spiral: r = θ', pad=20)
plt.colorbar(sc, ax=ax, label='Angle (radians)', pad=0.1)

Exercise 4. Create a radar chart (spider plot) comparing three students across 5 subjects. Each student has scores from 0–100 for Math, Science, English, History, and Art. Plot all three students on the same polar axes with different colors and use ax.fill() with low alpha. Explain why polar coordinates are natural for this comparison.

Solution to Exercise 4
import matplotlib.pyplot as plt
import numpy as np

categories = ['Math', 'Science', 'English', 'History', 'Art']
n = len(categories)
angles = np.linspace(0, 2 * np.pi, n, endpoint=False).tolist()
angles += angles[:1]  # close the polygon

students = {
    'Alice': [90, 85, 70, 65, 80],
    'Bob':   [60, 75, 85, 90, 70],
    'Carol': [80, 80, 80, 80, 80],
}

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
for name, scores in students.items():
    values = scores + scores[:1]
    ax.plot(angles, values, label=name)
    ax.fill(angles, values, alpha=0.1)

ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories)
ax.legend(loc='upper right')
ax.set_title('Student Comparison')
plt.show()

# Polar is natural here because each category is equidistant around
# a circle — there is no inherent ordering (Math is not "before"
# Science). The circular layout treats all subjects equally and makes
# shape comparison intuitive: a balanced student is a regular polygon.

Exercise 5. Explain why plotting y = sin(x) for x in [0, 10] in polar coordinates looks strange and misleading. What kind of data is polar appropriate for? Give one example of data that should NEVER be plotted in polar and explain why.

Solution to Exercise 5

sin(x) for x in [0, 10] in polar interprets x as an angle (radians) and sin(x) as a radius. The result is a looping curve that does not convey the periodic nature of sine — it distorts the familiar wave into an unrecognizable shape. The problem: x here represents a continuous independent variable, not a cyclic angle.

Polar is appropriate for:

  • Wind direction frequency (angle = compass bearing, radius = frequency)
  • Time-of-day activity (angle = hour, radius = count)
  • Phase angles in signal processing

Never use polar for:

  • Time series where time is linear (not cyclic) — e.g., stock prices over 5 years. Plotting this in polar wraps the time axis into a circle, destroying the chronological structure and making trends impossible to read. If the x-axis has a natural start and end (not a cycle), use Cartesian coordinates. plt.show()