Skip to content

Commit 78e2cf5

Browse files
committed
continued work on project
1 parent bd614c9 commit 78e2cf5

File tree

11 files changed

+393
-85
lines changed

11 files changed

+393
-85
lines changed

students/eowyn/windrevenue/windrevenue/UI.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,24 @@
1010

1111
class UI():
1212

13-
def __init__(self):
13+
def __init__(self, met=met, pct=pct, pricing=pricing):
1414
"""
1515
Instantiate classes to store state related to
1616
different data and funcionality.
1717
"""
18-
self.met = met()
19-
self.pct = pct()
20-
self.pricing = pricing()
18+
if met is not None:
19+
self.met = met
20+
else:
21+
self.met = met()
22+
if pct is not None:
23+
self.pct = pct
24+
else:
25+
self.pct = pct()
26+
if pricing is not None:
27+
self.pricing = pricing
28+
else:
29+
self.pricing = pricing()
30+
2131
self.peak = peak()
2232
self.rev = rev()
2333
self.ad = ad()

students/eowyn/windrevenue/windrevenue/align_data.py

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,29 @@
1818
"""
1919

2020
import pandas as pd
21-
import numpy as np
21+
#import pdb
22+
2223

2324
class AlignData():
25+
26+
@staticmethod
27+
def get_typical_year():
28+
# Construct a dummy TimeStamp index for the typical year
29+
return pd.date_range('2015-01-01', periods=8760, freq='H')
30+
2431
def __init__(self, price_data=None, met_data=None):
2532
self.pricing = price_data
2633
self.met = met_data
2734

2835
def resample_timeseries(self, timestep='60min'):
2936
"""
3037
Resample met, generation, and power pricing data to hourly
38+
TO DO: Handle missing vals introduced by re-indexing better
3139
"""
32-
self.power_hour = self.pricing.get_pricing_field().resample(timestep).mean()
33-
self.met_hour = self.met.get_wind_and_generation().resample(timestep).mean()
40+
repwr = self.pricing.get_pricing_field().resample(timestep).mean()
41+
self.power_hour = repwr.bfill()
42+
remet = self.met.get_wind_and_generation().resample(timestep).mean()
43+
self.met_hour = remet.bfill()
3444

3545
def determine_overlap(self):
3646
# Check if there of overlap between generation and pricing data
@@ -82,17 +92,19 @@ def calculate_typical_year(self):
8292
At this point, the datetime axis is no longer available.
8393
We will deal with leap-years then re-create the TimeStamp index.
8494
"""
95+
#pdb.set_trace()
8596
self.met_yr = self.met_hour.groupby([self.met_hour.index.month,
8697
self.met_hour.index.day,
8798
self.met_hour.index.hour]).mean()
8899
self.pwr_yr = self.power_hour.groupby([self.power_hour.index.month,
89100
self.power_hour.index.day,
90101
self.power_hour.index.hour]).mean()
91-
self.met_yr = self.met_yr.remove_leap_day()
92-
self.pwr_yr = self.pwr_yr.remove_leap_day()
102+
self.remove_leap_day()
93103
typical_year = self.get_typical_year()
94-
self.met_yr.index, self.met_yr.index.names = typical_year, ["TimeStamp"]
95-
self.pwr_yr.index, self.pwr_yr.index.names = typical_year, ["TimeStamp"]
104+
self.met_yr.index = typical_year
105+
self.met_yr.index.names = ["TimeStamp"]
106+
self.pwr_yr.index = typical_year
107+
self.pwr_yr.index.names = ["TimeStamp"]
96108

97109

98110
def remove_leap_day(self):
@@ -103,28 +115,26 @@ def remove_leap_day(self):
103115
Drop all Feb 29 rows:
104116
temp2 = temp.drop(temp.index[1416:1440], axis = 0)
105117
"""
106-
if len(self.met_hour.index) == 8784:
118+
if len(self.met_yr.index) == 8784:
107119
print("removing leap day from met year")
108-
self.met_yr = self.met_hour.drop(self.met_hour.index[1416:1440], axis=0)
109-
if len(self.power_hour.index) == 8784:
120+
self.met_yr = self.met_yr.drop(self.met_yr.index[1416:1440], axis=0)
121+
if len(self.pwr_yr.index) == 8784:
110122
print("removing leap day from power year")
111-
self.pwr_yr = self.power_hour.drop(self.power_hour.index[1416:1440], axis=0)
112-
113-
def get_typical_year(self):
114-
# Construct a dummy TimeStamp index for the typical year
115-
typical_year = pd.date_range('2015-01-01', periods=8760, freq='H')
123+
self.pwr_yr = self.pwr_yr.drop(self.pwr_yr.index[1416:1440], axis=0)
116124

117125
def align_data(self):
118126
"""
119-
Determine if there is a year of overlapping data, else,
127+
Determine if there is a year of overlapping data, else,
120128
calculate a typical year of data. Return single data frame
121129
of met and power on same time axis.
122130
"""
123131
self.resample_timeseries()
124-
self.remove_leap_day()
125132
if self.determine_overlap() and self.determine_amt_overlap():
133+
print("Calcuating Concurrent Year")
126134
self.calculate_same_year()
135+
self.calculate_typical_year()
127136
else:
137+
print("Calcutating Typical Year")
128138
self.calculate_typical_year()
129-
return pd.concat([self.met_hour, self.power_hour],axis = 1)
139+
return pd.concat([self.met_yr, self.pwr_yr], axis=1)
130140

students/eowyn/windrevenue/windrevenue/parse_met_data.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,6 @@ def __init__(self, fname=None, pct=None):
2525
def setPct(self, pct):
2626
self.pct = pct
2727

28-
# Round float value n to nearest precision
29-
# https://stackoverflow.com/questions/4265546/python-round-to-nearest-05
30-
def round_to_05(n, precision=0.5):
31-
correction = 0.5 if n >= 0 else -0.5
32-
return int( n/precision+correction ) * precision
33-
3428
def parse_met_file(self, fname=None):
3529
"""
3630
Read met data file, store in data frame and store sensor choice
@@ -53,13 +47,8 @@ def get_met_timeseries(self):
5347
currentdf = currentdf.dropna(axis=0, how='any')
5448
return currentdf
5549

56-
def dummy_function(self, fname=os.path.abspath("sample_data/sample_met.txt")):
57-
filename = input("Full path to met file (leave blank to use sample data):\n")
58-
fname = filename or fname
59-
6050
def load_new(self, fname):
61-
# Use sample data, or else read data from file provided by user
62-
filename = None
51+
# Read met data from file into data frame self.metdf
6352
print("Reading met file: ", fname)
6453
metdf = pd.read_table(fname, skiprows=1,
6554
index_col=0,

students/eowyn/windrevenue/windrevenue/peakhours.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,39 +8,48 @@
88
"""
99
import pandas as pd
1010

11+
1112
class PeakHours():
1213

13-
def __init__(self):
14-
self.peakHours = pd.Series([i for i in range(0, 12)]) # 0..11
15-
self.offPeakHours = pd.Series([i for i in range(12, 24)]) # 12..23
14+
def __init__(self, peak=None, offpeak=None):
15+
# PeakHours instances contain series of peak and off peak hours
16+
# By default those are 0-11 and 12-23, respectively
17+
# No restriction is made on the hours, but they should be lists
18+
if peak is not None:
19+
self.peak_hours = pd.Series(peak)
20+
else:
21+
self.peak_hours = pd.Series([i for i in range(0, 12)]) # 0..11
22+
if offpeak is not None:
23+
self.off_peak_hours = pd.Series(offpeak)
24+
else:
25+
self.off_peak_hours = pd.Series([i for i in range(12, 24)]) # 12..23
1626

1727
def print_peak_hours(self):
18-
print("Peak hours: ", self.peakHours)
19-
print("Off-Peak hours: ", self.offPeakHours)
28+
print("Peak hours: ", self.peak_hours)
29+
print("Off-Peak hours: ", self.off_peak_hours)
2030

2131
def get_peak_hours(self):
22-
return (self.peakHours, self.offPeakHours)
32+
return (self.peak_hours, self.off_peak_hours)
2333

2434
def set_peak_hours(self):
2535
"""
2636
Ask user to specify peak hours. Parse the input.
2737
Off-Peak are all the
28-
other hours on 24-hour basis. Set attributes for peak/offPeakHours
38+
other hours on 24-hour basis. Set attributes for peak/off_peak_hours
2939
"""
30-
choice = input("Enter peak hours (1-24) as list or range e.g.1,3,5-9\n")
40+
41+
choice = input("Enter peak hours (0-23) as list or range e.g.1,3,5-9\n")
3142
parsed = [i.split('-') for i in choice.split(',')]
32-
print(parsed)
3343
flat = set()
34-
print(flat)
3544
for elem in parsed:
3645
if len(elem) == 2:
37-
inclusive = set([i for i in range(int(elem[0]),int(elem[1])+1)])
46+
inclusive = set([i for i in range(int(elem[0]), int(elem[1])+1)])
3847
flat = flat.union(inclusive)
3948
else:
4049
flat.update([int(elem[0])])
4150
allhours = [i + 1 for i in range(0, 24)]
42-
self.offPeakHours = list(set(allhours).difference(flat))
43-
self.peakHours = list(flat)
51+
self.off_peak_hours = list(set(allhours).difference(flat))
52+
self.peak_hours = list(flat)
4453

4554

4655

students/eowyn/windrevenue/windrevenue/revenue.py

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,86 @@
1212

1313
import pandas as pd
1414
import numpy as np
15+
from windrevenue.peakhours import PeakHours
16+
from windrevenue.align_data import AlignData
1517

1618

17-
def GrossRevenue():
19+
class GrossRevenue():
20+
"""
21+
Generate monthly tables of gross revenue given a data frame of
22+
aligned (1-year, Jan - Dec) pricing and generation data. Include
23+
wind speed in the final tables that are printed and written
24+
to .csv file.
25+
"""
1826

19-
def calculate_revenue(self):
27+
def __init__(self, aligned_data, peak_hours=None):
2028
"""
29+
Require aligned pricing and generation data in AlignData obj
30+
Optionally, accept PeakHours object. Otherwise create one
31+
using default parameters.d
32+
"""
33+
if peak_hours is not None:
34+
self.peak_hours = peak_hours
35+
else:
36+
self.peak_hours = PeakHours()
37+
self.aligned_data = aligned_data
38+
39+
def add_revenue_column(self, scale=1e-4):
40+
"""
41+
ALignedData has 3 columns, wind speed, generation, and price
42+
Append a column with the gross revenue, scaling input power to
43+
MWh (default 1e-4)
44+
"""
45+
colnames = list(self.aligned_data)
46+
pricevar, genvar = colnames[1], colnames[2]
47+
revenue = scale * self.aligned_data[genvar] * self.aligned_data[pricevar]
48+
self.aligned_data["Revenue"] = revenue
49+
50+
def subset_data(self, subset_on="peak"):
51+
"""
52+
Return a dataframe containing only peak, or off-peak, times
53+
as determined by the arg "subset_on" which can be "peak" or
54+
"off-peak", with "peak" as the default. Other times are NaN
55+
"""
56+
subset_on = subset_on.strip().lower()
57+
hrsoptions = self.peak_hours.get_peak_hours() # Peak & off-peak
58+
# Select peak or off peak hours
59+
if subset_on == "peak":
60+
hours = hrsoptions[0]
61+
elif subset_on == "off-peak":
62+
hours = hrsoptions[1]
63+
else:
64+
raise(UserWarning, "Selection must be peak or off-peak")
65+
# Construct timeseries of 1, NaN for times included,excluded
66+
include_times = pd.Series(self.aligned_data.index.hour).isin(hours)
67+
# Set up time index to match aligned_data so we can mask
68+
include_times.index = AlignData.get_typical_year()
69+
include_times.index.names = ["TimeStamp"]
70+
include_times = include_times.apply(lambda x: 1 if x else np.nan)
71+
# Mask aligned_data to NaN-out excluded times
72+
return self.aligned_data.mul(include_times, axis=0)
2173

22-
:param self:
23-
:return:
74+
def group_data(self, input_df):
75+
"""
76+
Return a dataframe of the data grouped by month, and hour
77+
of day (latter is inherent in aligned_data) for a dataframe
78+
of windspeed, generation, price, and revenue. Average the
79+
windspeed and price; sum generation and revenue.
2480
"""
2581
pass
2682

83+
def save_pretty_table(self, outputfile="sample_output.csv"):
84+
"""
85+
Save a pretty table of results to outputfile (.csv)
86+
"""
87+
pass
88+
89+
def print_pretty_table(self):
90+
"""
91+
Print a pretty table of results to stdout
92+
"""
93+
pass
94+
95+
96+
97+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import unittest
5+
6+
from windrevenue.UI import UI
7+
from windrevenue.power_curve_tool import PowerCurve
8+
9+
pcfile = os.path.abspath("../windrevenue/sample_data/power_curve.txt")
10+
windbins = [2.5, 3.0, 3.5, 4.0,
11+
4.5, 5.0, 5.5, 6.0,
12+
6.5, 7.0, 7.5, 8.0,
13+
8.5, 9.0, 9.5, 10.0,
14+
10.5, 11.0, 11.5, 12.0,
15+
12.5, 13.0, 13.5, 14.0,
16+
14.5, 15.0, 15.5, 16.0,
17+
16.5, 17.0, 17.5, 18.0,
18+
18.5, 19.0, 19.5, 20.0]
19+
20+
def test_powercurve_init_with_fname():
21+
pc = PowerCurve(fname=pcfile)
22+
assert pc.power_curve[2.5] == 10
23+
assert pc.power_curve[7] == 1400
24+
25+
class InputFeeder():
26+
def __init__(self, input_lines):
27+
self.index = 0
28+
self.input_lines = input_lines
29+
30+
def get_user_input(self, prompt_string):
31+
if self.index >= len(self.input_lines):
32+
return None
33+
else:
34+
value = self.input_lines[self.index]
35+
self.index = self.index + 1
36+
return value
37+
38+
def test_load_powercurve_with_ui(capsys):
39+
input_lines = ["2", "3", pcfile, "4", "6"]
40+
input_feeder = InputFeeder(input_lines)
41+
from unittest import mock
42+
import builtins
43+
# with capsys.disabled():
44+
with mock.patch.object(UI, 'get_user_input', input_feeder.get_user_input):
45+
ui = UI()
46+
import pytest
47+
with pytest.raises(SystemExit) as pytest_wrapped_e:
48+
ui.mainloop()
49+
assert pytest_wrapped_e.type == SystemExit
50+
assert pytest_wrapped_e.value.code == 42
51+
captured = capsys.readouterr()
52+
assert "Reading power curve file" in captured.out
53+
# assert "Thank you, Kenny Powers, for your generosity and recent gift of $1000000.00.\n" == captured.out
54+
assert "" == captured.err
55+
print(captured.out)
56+
57+

0 commit comments

Comments
 (0)