Finding the normal pitch angle threshold#

import itertools

import matplotlib.pyplot as plt
import pandas as pd
# import data
df = pd.read_csv("data/SCADA_merged.csv", skip_blank_lines=True)
# list of turbines to plot
# plot these three turbines to visualise the difference
list1 = [1, 8, 25]
# plot for pitch angle = 0, 3, 5, 7 and 10 deg
for x in list1:
    # filter only data for turbine x
    dfx = df[(df["turbine"] == x)].copy()

    # filter out curtailment - curtailed when turbine is pitching
    # above (and/or below) a certain threshold
    # 0 deg - normal pitch
    def f0(c):
        if c["pitch"] == 0 or (
            c["pitch"] != 0
            and (
                c["ap_av"] <= (0.1 * dfx["ap_av"].max())
                or c["ap_av"] >= (0.9 * dfx["ap_av"].max())
            )
        ):
            return "normal"
        else:
            return "curtailed"

    dfx["curtailment0"] = dfx.apply(f0, axis=1)

    # 3 deg
    def f1(c):
        if 0 <= c["pitch"] <= 3 or (
            (c["pitch"] > 3 or c["pitch"] < 0)
            and (
                c["ap_av"] <= (0.1 * dfx["ap_av"].max())
                or c["ap_av"] >= (0.9 * dfx["ap_av"].max())
            )
        ):
            return "normal"
        else:
            return "curtailed"

    dfx["curtailment1"] = dfx.apply(f1, axis=1)

    # 5 deg
    def f2(c):
        if 0 <= c["pitch"] <= 5 or (
            (c["pitch"] > 5 or c["pitch"] < 0)
            and (
                c["ap_av"] <= (0.1 * dfx["ap_av"].max())
                or c["ap_av"] >= (0.9 * dfx["ap_av"].max())
            )
        ):
            return "normal"
        else:
            return "curtailed"

    dfx["curtailment2"] = dfx.apply(f2, axis=1)

    # 7 deg
    def f3(c):
        if 0 <= c["pitch"] <= 7 or (
            (c["pitch"] > 7 or c["pitch"] < 0)
            and (
                c["ap_av"] <= (0.1 * dfx["ap_av"].max())
                or c["ap_av"] >= (0.9 * dfx["ap_av"].max())
            )
        ):
            return "normal"
        else:
            return "curtailed"

    dfx["curtailment3"] = dfx.apply(f3, axis=1)

    # 10 deg
    def f4(c):
        if 0 <= c["pitch"] <= 10 or (
            (c["pitch"] > 10 or c["pitch"] < 0)
            and (
                c["ap_av"] <= (0.1 * dfx["ap_av"].max())
                or c["ap_av"] >= (0.9 * dfx["ap_av"].max())
            )
        ):
            return "normal"
        else:
            return "curtailed"

    dfx["curtailment4"] = dfx.apply(f4, axis=1)

    # filter data for plots
    # normal w/o curtailment 0 deg
    df1 = dfx[dfx.curtailment0 == "normal"]
    # normal w/o curtailment 3 deg
    df2 = dfx[dfx.curtailment1 == "normal"]
    # normal w/o curtailment 5 deg
    df3 = dfx[dfx.curtailment2 == "normal"]
    # normal w/o curtailment 7 deg
    df4 = dfx[dfx.curtailment3 == "normal"]
    # normal w/o curtailment 10 deg
    df5 = dfx[dfx.curtailment4 == "normal"]

    # get x and y coordinates
    # normal w/ curtailment
    x1 = dfx["ws_av"]
    y1 = dfx["ap_av"]
    # normal w/o curtailment 0 deg pitch
    x2 = df1["ws_av"]
    y2 = df1["ap_av"]
    # normal w/o curtailment 3 deg
    x3 = df2["ws_av"]
    y3 = df2["ap_av"]
    # normal w/o curtailment 5 deg
    x4 = df3["ws_av"]
    y4 = df3["ap_av"]
    # normal w/o curtailment 7 deg
    x5 = df4["ws_av"]
    y5 = df4["ap_av"]
    # normal w/o curtailment 10 deg
    x6 = df5["ws_av"]
    y6 = df5["ap_av"]

    # plot the figure
    fig = plt.figure(figsize=(18, 9), dpi=1500)

    ax1 = fig.add_subplot(231)
    ax1.scatter(x1, y1, marker=".", c="C0")
    plt.xlabel("Wind speed (m/s)")
    plt.ylabel("Average active power (kW)")
    plt.title("w/ curtailment")

    ax2 = fig.add_subplot(232)
    ax2.scatter(x2, y2, marker=".", c="C1")
    plt.xlabel("Wind speed (m/s)")
    plt.ylabel("Average active power (kW)")
    plt.title("w/o curtailment (normal pitch =0)")

    ax3 = fig.add_subplot(233)
    ax3.scatter(x3, y3, marker=".", c="C2")
    plt.xlabel("Wind speed (m/s)")
    plt.ylabel("Average active power (kW)")
    plt.title("w/o curtailment (0<= normal pitch <=3)")

    ax4 = fig.add_subplot(234)
    ax4.scatter(x4, y4, marker=".", c="C3")
    plt.xlabel("Wind speed (m/s)")
    plt.ylabel("Average active power (kW)")
    plt.title("w/o curtailment (0<= normal pitch <=5)")

    ax5 = fig.add_subplot(235)
    ax5.scatter(x5, y5, marker=".", c="C4")
    plt.xlabel("Wind speed (m/s)")
    plt.ylabel("Average active power (kW)")
    plt.title("w/o curtailment (0<= normal pitch <=7)")

    ax6 = fig.add_subplot(236)
    ax6.scatter(x6, y6, marker=".", c="C5")
    plt.xlabel("Wind speed (m/s)")
    plt.ylabel("Average active power (kW)")
    plt.title("w/o curtailment (0<= normal pitch <=10)")

    fig.suptitle(
        "Power curves for turbine %s" % x + " at different normal pitch angles"
    )
    plt.tight_layout()
    plt.subplots_adjust(top=0.92)
    plt.show()
../../_images/be0e61b9f779e76d73c5aec365361f26445a85295780a27c9bb1728084ed4718.png ../../_images/9940bc8e0a7c6c617bb054d118a3c7ad0b94a7bcca8d86fd843c1246b1188acb.png ../../_images/1c892a88454f211b773f8e4551b044486b9956014d82bdb0ffc3664e503b6327.png
# plot for pitch angle = 2.5, 3.5, 4, 4.5, 5.5 and 6 deg
for x in list1:
    dfx = df[(df["turbine"] == x)].copy()

    # 2.5 deg
    def f5(c):
        if 0 <= c["pitch"] <= 2.5 or (
            (c["pitch"] > 2.5 or c["pitch"] < 0)
            and (
                c["ap_av"] <= (0.1 * dfx["ap_av"].max())
                or c["ap_av"] >= (0.9 * dfx["ap_av"].max())
            )
        ):
            return "normal"
        else:
            return "curtailed"

    dfx["curtailment5"] = dfx.apply(f5, axis=1)

    # 3.5 deg
    def f6(c):
        if 0 <= c["pitch"] <= 3.5 or (
            (c["pitch"] > 3.5 or c["pitch"] < 0)
            and (
                c["ap_av"] <= (0.1 * dfx["ap_av"].max())
                or c["ap_av"] >= (0.9 * dfx["ap_av"].max())
            )
        ):
            return "normal"
        else:
            return "curtailed"

    dfx["curtailment6"] = dfx.apply(f6, axis=1)

    # 4 deg
    def f7(c):
        if 0 <= c["pitch"] <= 4 or (
            (c["pitch"] > 4 or c["pitch"] < 0)
            and (
                c["ap_av"] <= (0.1 * dfx["ap_av"].max())
                or c["ap_av"] >= (0.9 * dfx["ap_av"].max())
            )
        ):
            return "normal"
        else:
            return "curtailed"

    dfx["curtailment7"] = dfx.apply(f7, axis=1)

    # 4.5 deg
    def f8(c):
        if 0 <= c["pitch"] <= 4.5 or (
            (c["pitch"] > 4.5 or c["pitch"] < 0)
            and (
                c["ap_av"] <= (0.1 * dfx["ap_av"].max())
                or c["ap_av"] >= (0.9 * dfx["ap_av"].max())
            )
        ):
            return "normal"
        else:
            return "curtailed"

    dfx["curtailment8"] = dfx.apply(f8, axis=1)

    # 5.5 deg
    def f9(c):
        if 0 <= c["pitch"] <= 5.5 or (
            (c["pitch"] > 5.5 or c["pitch"] < 0)
            and (
                c["ap_av"] <= (0.1 * dfx["ap_av"].max())
                or c["ap_av"] >= (0.9 * dfx["ap_av"].max())
            )
        ):
            return "normal"
        else:
            return "curtailed"

    dfx["curtailment9"] = dfx.apply(f9, axis=1)

    # 6 deg
    def f10(c):
        if 0 <= c["pitch"] <= 6 or (
            (c["pitch"] > 6 or c["pitch"] < 0)
            and (
                c["ap_av"] <= (0.1 * dfx["ap_av"].max())
                or c["ap_av"] >= (0.9 * dfx["ap_av"].max())
            )
        ):
            return "normal"
        else:
            return "curtailed"

    dfx["curtailment10"] = dfx.apply(f10, axis=1)

    # normal w/o curtailment 2.5 deg
    df1 = dfx[dfx.curtailment5 == "normal"]
    # normal w/o curtailment 3.5 deg
    df2 = dfx[dfx.curtailment6 == "normal"]
    # normal w/o curtailment 4 deg
    df3 = dfx[dfx.curtailment7 == "normal"]
    # normal w/o curtailment 4.5 deg
    df4 = dfx[dfx.curtailment8 == "normal"]
    # normal w/o curtailment 5.5 deg
    df5 = dfx[dfx.curtailment9 == "normal"]
    # normal w/o curtailment 6 deg
    df6 = dfx[dfx.curtailment10 == "normal"]

    # normal w/o curtailment 2.5 deg pitch
    x1 = df1["ws_av"]
    y1 = df1["ap_av"]
    # normal w/o curtailment 3.5 deg
    x2 = df2["ws_av"]
    y2 = df2["ap_av"]
    # normal w/o curtailment 4 deg
    x3 = df3["ws_av"]
    y3 = df3["ap_av"]
    # normal w/o curtailment 4.5 deg
    x4 = df4["ws_av"]
    y4 = df4["ap_av"]
    # normal w/o curtailment 5.5 deg
    x5 = df5["ws_av"]
    y5 = df5["ap_av"]
    # normal w/o curtailment 6 deg
    x6 = df6["ws_av"]
    y6 = df6["ap_av"]

    fig = plt.figure(figsize=(18, 9), dpi=1500)

    ax1 = fig.add_subplot(231)
    ax1.scatter(x1, y1, marker=".", c="C0")
    plt.xlabel("Wind speed (m/s)")
    plt.ylabel("Average active power (kW)")
    plt.title("w/o curtailment (0<= normal pitch <=2.5)")

    ax2 = fig.add_subplot(232)
    ax2.scatter(x2, y2, marker=".", c="C1")
    plt.xlabel("Wind speed (m/s)")
    plt.ylabel("Average active power (kW)")
    plt.title("w/o curtailment (0<= normal pitch <=3.5)")

    ax3 = fig.add_subplot(233)
    ax3.scatter(x3, y3, marker=".", c="C2")
    plt.xlabel("Wind speed (m/s)")
    plt.ylabel("Average active power (kW)")
    plt.title("w/o curtailment (0<= normal pitch <=4)")

    ax4 = fig.add_subplot(234)
    ax4.scatter(x4, y4, marker=".", c="C3")
    plt.xlabel("Wind speed (m/s)")
    plt.ylabel("Average active power (kW)")
    plt.title("w/o curtailment (0<= normal pitch <=4.5)")

    ax5 = fig.add_subplot(235)
    ax5.scatter(x5, y5, marker=".", c="C4")
    plt.xlabel("Wind speed (m/s)")
    plt.ylabel("Average active power (kW)")
    plt.title("w/o curtailment (0<= normal pitch <=5.5)")

    ax6 = fig.add_subplot(236)
    ax6.scatter(x6, y6, marker=".", c="C5")
    plt.xlabel("Wind speed (m/s)")
    plt.ylabel("Average active power (kW)")
    plt.title("w/o curtailment (0<= normal pitch <=6)")

    fig.suptitle(
        "Power curves for turbine %s" % x + " at different normal pitch angles"
    )
    plt.tight_layout()
    plt.subplots_adjust(top=0.92)
    plt.show()
../../_images/3311a15f4e66a238dcc95e8d89a61822776241269d8b8c301267bc3fc7fecf99.png ../../_images/1aadfd4e9af4e544a017fcb2534286109910e75d456c05e7961fbed7023b9dec.png ../../_images/03e2c99288eb7e53cf44c22c9177931d170fd79e9affc0473ffcd1b01b91c80c.png
# plot for all turbines with a threshold of 3.5 deg
# list of turbines to plot
list2 = list(df["turbine"].unique())
# sort turbines in ascending order
list2 = sorted(list2, key=int)
# create new empty dataframe with the same index
df2 = pd.DataFrame(index=df.index)
# apply function for filtering curtailment
for x in list2:
    dfx = df[(df["turbine"] == x)].copy()

    # 3.5 deg
    def f(c):
        if 0 <= c["pitch"] <= 3.5 or (
            (c["pitch"] > 3.5 or c["pitch"] < 0)
            and (
                c["ap_av"] <= (0.1 * dfx["ap_av"].max())
                or c["ap_av"] >= (0.9 * dfx["ap_av"].max())
            )
        ):
            return c["ap_av"]

    df2["%s" % x] = dfx.apply(f, axis=1)
# copy wind speed from original dataframe
df2["Wind speed (m/s)"] = df["ws_av"]
# setting wind speed as index
df2 = df2.set_index("Wind speed (m/s)")
# create new list for individual subplot titles
list3 = ["Power curve for turbine %s without curtailment" % x for x in list2]
# plotting all columns (all turbines) in the same figure
ax = df2.plot(
    subplots=True,
    style=".",
    layout=(5, 5),
    figsize=(50, 30),
    sharex=False,
    title=list3,
    legend=False,
    xlim=(0, 38),
)
# based on layout=(x, y), create lists with range(0, x) and (0, y)
list4 = list(range(0, 5))
# get coordinates of each subplot
list5 = list(itertools.product(list4, list4))
for x, y in list5:
    ax[x][y].set_ylabel("Average active power (kW)")
../../_images/15dbd38fcd999c814538fae364ff23226fb2f0697d348d87ae14df244c55c210.png
# frequency of pitch angles
# round pitch angle to nearest degree
df["pitch_round"] = df["pitch"].round()
dfg = pd.DataFrame(df.groupby("pitch_round").size())
dfg = dfg.sort_values([0], ascending=False)
# get percentage of frequency of pitch angle
dfg["pct"] = dfg[0] / dfg[0].sum() * 100
dfg
0 pct
pitch_round
0.0 1860480 58.711186
70.0 276033 8.710776
87.0 168835 5.327928
1.0 113249 3.573800
90.0 80266 2.532955
2.0 54123 1.707960
91.0 53477 1.687574
92.0 36593 1.154766
3.0 34269 1.081427
4.0 27782 0.876717
5.0 24818 0.783182
6.0 23143 0.730324
8.0 22213 0.700976
7.0 22092 0.697157
9.0 20394 0.643574
10.0 18880 0.595796
11.0 17655 0.557139
12.0 16467 0.519649
13.0 15673 0.494593
14.0 14167 0.447068
15.0 12921 0.407748
16.0 11359 0.358456
17.0 9592 0.302695
18.0 8804 0.277828
89.0 7998 0.252393
19.0 6905 0.217901
61.0 6867 0.216702
60.0 6834 0.215661
22.0 6385 0.201492
20.0 6065 0.191393
... ... ...
281.0 1 0.000032
-160.0 1 0.000032
288.0 1 0.000032
-175.0 1 0.000032
-185.0 1 0.000032
-202.0 1 0.000032
307.0 1 0.000032
239.0 1 0.000032
229.0 1 0.000032
-101.0 1 0.000032
-65.0 1 0.000032
-6.0 1 0.000032
-9.0 1 0.000032
-10.0 1 0.000032
-22.0 1 0.000032
-24.0 1 0.000032
-29.0 1 0.000032
-57.0 1 0.000032
-60.0 1 0.000032
-68.0 1 0.000032
233.0 1 0.000032
-73.0 1 0.000032
221.0 1 0.000032
-89.0 1 0.000032
-93.0 1 0.000032
228.0 1 0.000032
-96.0 1 0.000032
-99.0 1 0.000032
232.0 1 0.000032
327.0 1 0.000032

351 rows × 2 columns

# finding power threshold before cut-in
# extract power readings before cut-in (3m/s)


def f_(c):
    if c["ws_av"] <= 3:
        return c["ap_av"]


df["power"] = df.apply(f_, axis=1)
# create pivot table (new dataframe)
p = pd.pivot_table(df, index=["ws_av"], columns=["turbine"], values=["power"])
# removing pivot table values name from heading
p.columns = p.columns.droplevel(0)
list6 = ["Power curve for turbine %s before cut-in" % x for x in list2]
# rename index
p.index.name = "Wind speed (m/s)"
ax = p.plot(
    subplots=True,
    figsize=(50, 30),
    layout=(5, 5),
    style=".",
    sharex=False,
    title=list6,
    legend=False,
    xlim=(0, 3),
    ylim=(-40, 200),
)
for x, y in list5:
    ax[x][y].set_ylabel("Average active power (kW)")
../../_images/f5fe8862603159b2b4b78791e892516f2555189e8c209ba3b9c7e514efb9a8f0.png
# plot for turbine 24 to visualise the difference
# list of turbines to plot
list1 = [24]
for x in list1:
    # filter only data for turbine x
    dfx = df[(df["turbine"] == x)].copy()

    # get x and y coordinates
    # normal w/ curtailment
    x1 = dfx["ws_av"]
    y1 = dfx["ap_av"]

    # plot the figure
    fig = plt.figure(figsize=(7, 4.5), dpi=500)

    ax1 = fig.add_subplot(111)
    ax1.scatter(x1, y1, marker=".", c="C0")
    plt.xlabel("Wind speed (m/s)")
    plt.ylabel("Average active power (kW)")
    plt.show()
../../_images/a7c1dee2610025ec84114783aa35900287be798c6cb23bccff0bbf626946e31d.png
for x in list1:
    # filter only data for turbine x
    dfx = df[(df["turbine"] == x)].copy()

    def f_x(c):
        if c["ws_av"] < 3 and c["ap_av"] > 0:
            return 0
        else:
            return c["ap_av"]

    dfx["ap"] = dfx.apply(f_x, axis=1)

    # get x and y coordinates
    # normal w/ curtailment
    x1 = dfx["ws_av"]
    y1 = dfx["ap"]

    # plot the figure
    fig = plt.figure(figsize=(7, 4.5), dpi=500)

    ax1 = fig.add_subplot(111)
    ax1.scatter(x1, y1, marker=".", c="C0")
    plt.xlabel("Wind speed (m/s)")
    plt.ylabel("Average active power (kW)")
    plt.show()
../../_images/29328533f3e3dd4446d69c9d4473d46baf2441de768340aa51f3a6fe82112adb.png