Skip to content

Commit 3299f26

Browse files
committed
added ability to do non-density wind roses, defaulting to density.
1 parent e79c100 commit 3299f26

2 files changed

Lines changed: 50 additions & 22 deletions

File tree

Python_Engine/Python/src/python_toolkit/plot/polar.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from .utilities import process_polar_data, format_polar_plot
1616

1717

18-
def plot_polar(
18+
def polar(
1919
data: pd.DataFrame,
2020
value_column: str = "Value",
2121
direction_column: str = "Direction",
@@ -27,6 +27,7 @@ def plot_polar(
2727
legend: bool = True,
2828
ylim: tuple[float] = None,
2929
label: bool = False,
30+
density: bool = True,
3031
) -> plt.Axes:
3132
"""Create a polar plot showing frequencies by direction.
3233
@@ -55,7 +56,8 @@ def plot_polar(
5556
The y-axis limits. Defaults to None.
5657
label (bool, optional):
5758
Set to False to remove the bin labels. Defaults to False.
58-
59+
density (bool, optional):
60+
Set to False to see the sum of the values instead of their frequency
5961
Returns:
6062
plt.Axes: The axes object.
6163
"""
@@ -64,14 +66,13 @@ def plot_polar(
6466
_, ax = plt.subplots(subplot_kw={"projection": "polar"})
6567

6668
# create grouped data for plotting
67-
68-
#TOM NOTE: need to find out exactly what this does. Likely it's just a dataframe pivot where each column is a values bin and the index is the direction bins (from the number of directions). Density seems to make values between 0-100%. remove_calm removes values below 0.1, though this should be implemented in an argument for this method instead.
6969
binned = process_polar_data(
7070
data,
7171
value_column,
7272
direction_column,
7373
directions,
74-
value_bins
74+
value_bins,
75+
density
7576
)
7677

7778
# set colors
@@ -95,6 +96,7 @@ def plot_polar(
9596
rect = [0.1, 0.1, 0.8, 0.8]
9697
hist_ax = plt.Axes(fig, rect)
9798
hist_ax.bar(np.array([1]), np.array([1]))
99+
# END HACK
98100

99101
if title is None or title == "":
100102
ax.set_title(textwrap.fill(f"{value_column}", 75))
@@ -129,7 +131,7 @@ def plot_polar(
129131
# construct legend
130132
if legend:
131133
handles = [
132-
mpatches.Patch(color=colors[n], label=f"{i} to {j}")
134+
mpatches.Patch(color=colors[n], label=(f"{i} to {j}" if str(j) != str(np.inf) else f"{i} and above"))
133135
for n, (i, j) in enumerate(binned.columns.values)
134136
]
135137
_ = ax.legend(
@@ -147,10 +149,14 @@ def plot_polar(
147149
# set y-axis limits
148150
if ylim is None:
149151
ylim = (0, max(binned.sum(axis=1)))
150-
if len(ylim) != 2:
152+
ax.set_ylim(ylim)
153+
elif len(ylim) != 2:
151154
raise ValueError("ylim must be a tuple of length 2.")
152-
ax.set_ylim(ylim)
153-
ax.yaxis.set_major_formatter(mticker.PercentFormatter(xmax=1))
155+
else:
156+
ax.set_ylim(ylim)
157+
158+
if density:
159+
ax.yaxis.set_major_formatter(mticker.PercentFormatter(xmax=1))
154160

155161
format_polar_plot(ax, yticklabels=True)
156162

Python_Engine/Python/src/python_toolkit/plot/utilities.py

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from matplotlib.tri import Triangulation
2727
from PIL import Image
2828
from ..bhom.analytics import bhom_analytics
29+
from ..bhom.logging import CONSOLE_LOGGER
2930

3031
@bhom_analytics()
3132
def average_color(colors: Any, keep_alpha: bool = False) -> str:
@@ -598,7 +599,16 @@ def format_polar_plot(ax: plt.Axes, yticklabels: bool = True) -> plt.Axes:
598599
if not yticklabels:
599600
ax.set_yticklabels([])
600601

601-
def process_polar_data(data:pd.DataFrame, values_column:str, directions_column:str, directions:int=36, value_bins:List[float]=None):
602+
def process_polar_data(data:pd.DataFrame, values_column:str, directions_column:str, directions:int=36, value_bins:List[float]=None, density:bool=True):
603+
"""Process data for a polar plot by grouping by value and direction bins, either as value counts, or sums (determined by density)
604+
605+
"""
606+
if values_column not in data.columns:
607+
raise ValueError(f"Values column `{values_column}` could not be found in the input dataframe.")
608+
609+
if directions_column not in data.columns:
610+
raise ValueError(f"Directions column `{directions_column}` could not be found in the input dataframe")
611+
602612
if value_bins is None:
603613
value_bins = np.linspace(min(data[values_column]), max(data[values_column]), 11)
604614

@@ -646,19 +656,30 @@ def process_polar_data(data:pd.DataFrame, values_column:str, directions_column:s
646656
index=dir_categories.index,
647657
name=dir_categories.name,
648658
)
649-
650-
df = pd.concat([dir_categories, categories], axis=1)
651-
652-
#remove calm?
659+
660+
df = pd.concat([dir_categories, categories, values_ser], axis=1)
661+
df.columns = [df.columns[0], df.columns[1], "Value"]
653662

654663
# pivot dataframe
655-
df = (
656-
df.groupby([df.columns[0], df.columns[1]], observed=True)
657-
.value_counts()
658-
.unstack()
659-
.fillna(0)
660-
.astype(int)
661-
)
664+
if density:
665+
df = (
666+
df.groupby([df.columns[0], df.columns[1]], observed=True)
667+
.value_counts()
668+
.unstack()
669+
.fillna(0)
670+
.astype(int)
671+
)
672+
else:
673+
#This allows plots like radiation roses, where the sum of the radiation from that direction is more useful than the counts
674+
df = (
675+
df.groupby([df.columns[0], df.columns[1]], observed=True)
676+
.sum()
677+
.unstack()
678+
.fillna(0)
679+
.astype(float)
680+
)
681+
682+
df = df.droplevel(level=0, axis=1)
662683

663684
for b in bin_tuples:
664685
if b not in df.columns:
@@ -671,6 +692,7 @@ def process_polar_data(data:pd.DataFrame, values_column:str, directions_column:s
671692
df.sort_index(axis=1, inplace=True)
672693
df = df.T
673694

674-
df = df / df.values.sum() #as density plot
695+
if density:
696+
df = df / df.values.sum() #show density values as a percentage of the total number of values.
675697

676698
return df

0 commit comments

Comments
 (0)