Search code examples
pythonmatplotlibplotpie-chartgeopandas

Spatial pie chart using geopandas


I am trying to make a pie chart that looks like the below enter image description here-

I am using geopandas for that-

us_states = gpd.read_file("conus_state.shp")
data = gpd.read_file("data_file.shp")

fig, ax = plt.subplots(figsize= (10,10))
us_states.plot(color = "None", ax = ax)
data.plot(column = ["Column1","Column2"], ax= ax, kind = "pie",subplots=True)

This gives me the following error-

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
C:\Users\LSRATH~1.STU\AppData\Local\Temp/ipykernel_17992/1047905594.py in <module>
      1 fig, ax = plt.subplots(figsize= (10,10))
      2 us_states.plot(color = "None", ax = ax)
----> 3 diff_env.plot(column = ["WS_MON1","WS_MON2"], ax= ax, kind = "pie")

c:\python38\lib\site-packages\geopandas\plotting.py in __call__(self, *args, **kwargs)
    951         if kind in self._pandas_kinds:
    952             # Access pandas plots
--> 953             return PlotAccessor(data)(kind=kind, **kwargs)
    954         else:
    955             # raise error

c:\python38\lib\site-packages\pandas\plotting\_core.py in __call__(self, *args, **kwargs)
    921             if isinstance(data, ABCDataFrame):
    922                 if y is None and kwargs.get("subplots") is False:
--> 923                     raise ValueError(
    924                         f"{kind} requires either y column or 'subplots=True'"
    925                     )

ValueError: pie requires either y column or 'subplots=True'

Even after specifying, subplots = True, it does not work.

How can I make a pie chart using 2 columns of the dataframe?

Below are the first five rows of the relevant columns-

diff_env[["Column1", "Column2", "geometry"]].head().to_dict()

{'Column1': {0: 2, 1: 0, 2: 0, 3: 1, 4: 12},
 'Column2': {0: 2, 1: 0, 2: 0, 3: 1, 4: 12},
 'geometry': {0: <shapely.geometry.point.Point at 0x2c94e07f190>,
  1: <shapely.geometry.point.Point at 0x2c94e07f130>,
  2: <shapely.geometry.point.Point at 0x2c94e07f0d0>,
  3: <shapely.geometry.point.Point at 0x2c94bb86d30>,
  4: <shapely.geometry.point.Point at 0x2c94e07f310>}}

Solution

  • sample data

    value0 value1 geometry size
    0 5 3 POINT (-105.96116535117056 31.014979334448164) 312
    1 2 3 POINT (-79.70609244147155 36.46222924414716) 439
    2 4 7 POINT (-68.89518006688962 37.84436728093645) 363
    3 7 9 POINT (-118.12344177257525 31.909303946488293) 303
    4 2 7 POINT (-102.1001252173913 28.57591221070234) 326
    5 3 3 POINT (-96.88772103678929 47.76324025083612) 522
    6 5 8 POINT (-112.33188157190635 48.16975143812709) 487
    7 7 6 POINT (-95.15025297658862 44.59245298996656) 594
    8 3 1 POINT (-100.36265715719063 46.787613401337794) 421
    9 2 4 POINT (-81.82966451505015 35.161393444816056) 401

    full code

    import geopandas as gpd
    import numpy as np
    import shapely
    import matplotlib.pyplot as plt
    
    states = (
        gpd.read_file(
            "https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_110m_admin_1_states_provinces.geojson"
        )
        .loc[lambda d: d["iso_3166_2"].ne("US-AK"), "geometry"]
        .exterior
    )
    
    # geodataframe of points where pies are to be plotted
    n = 10
    pies = gpd.GeoDataFrame(
        geometry=[
            shapely.geometry.Point(xy)
            for xy in zip(
                np.random.choice(np.linspace(*states.total_bounds[[0, 2]], 300), n),
                np.random.choice(np.linspace(*states.total_bounds[[1, 3]], 300), n),
            )
        ],
        data={f"value{c}": np.random.randint(1, 10, n) for c in range(2)},
        crs=states.crs,
    ).pipe(lambda d: d.assign(size=np.random.randint(300, 600, n)))
    
    # utility function inspired by https://stackoverflow.com/questions/56337732/how-to-plot-scatter-pie-chart-using-matplotlib
    def draw_pie(dist, xpos, ypos, size, ax):
        # for incremental pie slices
        cumsum = np.cumsum(dist)
        cumsum = cumsum / cumsum[-1]
        pie = [0] + cumsum.tolist()
    
        colors = ["blue", "red", "yellow"]
        for i, (r1, r2) in enumerate(zip(pie[:-1], pie[1:])):
            angles = np.linspace(2 * np.pi * r1, 2 * np.pi * r2)
            x = [0] + np.cos(angles).tolist()
            y = [0] + np.sin(angles).tolist()
    
            xy = np.column_stack([x, y])
            ax.scatter([xpos], [ypos], marker=xy, s=size, color=colors[i], alpha=1)
    
        return ax
    
    
    fig, ax = plt.subplots()
    
    ax = states.plot(ax=ax, edgecolor="black", linewidth=0.5)
    for _, r in pies.iterrows():
        ax = draw_pie([r.value0, r.value1], r.geometry.x, r.geometry.y, r["size"], ax)
    

    output

    enter image description here