FlowStatistics.java [src/java/m/cfa] Revision: 6b58cc57283496d8dcd336490b705c8a4da969a9 Date: Wed May 31 11:22:27 MDT 2017
package m.cfa;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
/**
* Last Updated: 31-May-2017
* @author Tyler Wible
* @since 29-June-2011
*/
public class FlowStatistics {
//Gets
public String getFlowStatistics_summary() {
return "flow_statistics.csv";
}
/**
* Calculates the statistics (min, max) for various numbers of consecutive
* days of daily data and other hydrologic indicators of alteration.
* Additionally, this calculates the same statistics for each year within
* the dataset
* @param directory the file path where the summary will be saved
* @param stationID the station ID for the current station, used to label the output
* @param stationName the station name for the current station, used to label the output
* @param flowData the String[][] containing sorted data for the time series
* (column 1 = dates (yyyy-mm-dd) column 2 = value
* @param highPercentile the percentile value for defining a "high flow pulse"
* typically greater than 75% = 0.75
* @param lowPercentile the percentile value for defining a "low flow pulse"
* typically lower than 25% = 0.25
* @param m used to calculate an "m-day-average" flow
* @param n used to calculate an "m-day-average" low flow which has a
* linearly interpolated recurrence interval of "n" years
* @param showMonthlyTF a flag to show monthly statistics (min/max/etc.) or not (which reduces the total output of the tool)
* @param seasonBegin the month and date (MM-dd) of the start of a seasonal analysis to be averaged over the period of data
* @param seasonEnd the month and date (MM-dd) of the end of a seasonal analysis to be averaged over the period of data
* @param period1Begin the begin date of the first period of analysis (yyyy-MM-dd), if blank ("") then no period 1 analysis is performed
* @param period1End the end date of the first period of analysis (yyyy-MM-dd), if blank ("") then no period 1 analysis is performed
* @param period2Begin the begin date of the second period of analysis (yyyy-MM-dd), if blank ("") then no period 2 analysis is performed
* @param period2End the end date of the first period of analysis (yyyy-MM-dd), if blank ("") then no period 2 analysis is performed
* @param period3Begin the begin date of the third period of analysis (yyyy-MM-dd), if blank ("") then no period 3 analysis is performed
* @param period3End the end date of the first period of analysis (yyyy-MM-dd), if blank ("") then no period 3 analysis is performed
* @param waterYearTF if true, it calculates statistics per water year (10/1 through 9/30), if false it calculates statistics per calendar year (1/1 through 12/31)
* @return an Object[] where returnArray[0] = flow value of "m-day-average
* n-year recurrence low flow"
* returnValue[1] = the error message (if any) associated with calculating
* the mQn flow
* @throws IOException
* @throws ParseException
*/
public Object[] calculateAllStatisticsSummaries(String directory,
String stationID,
String stationName,
String[][] flowData,
double highPercentile,
double lowPercentile,
int m,
int n,
boolean showMonthlyTF,
String seasonBegin,
String seasonEnd,
String period1Begin,
String period1End,
String period2Begin,
String period2End,
String period3Begin,
String period3End,
boolean waterYearTF) throws IOException, ParseException{
//Get today's date for output purposes
Date currentDate = new Date();
SimpleDateFormat outputOnlyDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
String today = outputOnlyDateFormat.format(currentDate);
stationName = stationName.replace(",", "");
//Initialize the summary result table
int summarySize = 65;
if(showMonthlyTF) summarySize = 197;
String[][] statsSummaryTable = new String[summarySize][1];
statsSummaryTable[0][0] = "Flow Statistics for " + stationID + "; " + stationName + "; created on " + today;
statsSummaryTable[1][0] = "Analysis Period (calendar year)";
statsSummaryTable[2][0] = "Count";
statsSummaryTable[3][0] = "Maximum [cfs]";
statsSummaryTable[4][0] = "Minimum [cfs]";
statsSummaryTable[5][0] = "Upper Quartile [cfs]";
statsSummaryTable[6][0] = "Lower Quartile [cfs]";
statsSummaryTable[7][0] = "Median [cfs]";
statsSummaryTable[8][0] = "Mean [cfs]";
statsSummaryTable[9][0] = "Standard Deviation [cfs]";
statsSummaryTable[10][0] = "Variance";
statsSummaryTable[11][0] = "Skewness";
statsSummaryTable[12][0] = "Coefficient of Variation";
statsSummaryTable[13][0] = "Maximum (1-day) [cfs]";
statsSummaryTable[14][0] = "Date of Maximum (1-day)";
statsSummaryTable[15][0] = "Minimum (1-day) [cfs]";
statsSummaryTable[16][0] = "Date of Minimum (1-day)";
statsSummaryTable[17][0] = "Maximum (3-day) [cfs]";
statsSummaryTable[18][0] = "Dates of Maximum (3-day)";
statsSummaryTable[19][0] = "Minimum (3-day) [cfs]";
statsSummaryTable[20][0] = "Dates of Minimum (3-day)";
statsSummaryTable[21][0] = "Maximum (7-day) [cfs]";
statsSummaryTable[22][0] = "Dates of Maximum (7-day)";
statsSummaryTable[23][0] = "Minimum (7-day) [cfs]";
statsSummaryTable[24][0] = "Dates of Minimum (7-day)";
statsSummaryTable[25][0] = "Minimum (7-day) / Annual Mean [cfs]";
statsSummaryTable[26][0] = "Maximum (30-day) [cfs]";
statsSummaryTable[27][0] = "Dates of Maximum (30-day)";
statsSummaryTable[28][0] = "Minimum (30-day) [cfs]";
statsSummaryTable[29][0] = "Dates of Minimum (30-day)";
statsSummaryTable[30][0] = "Maximum (90-day) [cfs]";
statsSummaryTable[31][0] = "Dates of Maximum (90-day)";
statsSummaryTable[32][0] = "Minimum (90-day) [cfs]";
statsSummaryTable[33][0] = "Dates of Minimum (90-day)";
statsSummaryTable[34][0] = "Number of Zero Flow Days";
statsSummaryTable[35][0] = "Number of Flow Reversals";
statsSummaryTable[36][0] = "Number of Flow Rises";
statsSummaryTable[37][0] = "Number of Flow Falls";
statsSummaryTable[38][0] = "Number of High Pulses (> " + highPercentile + " percentile)";
statsSummaryTable[39][0] = "Threshold for High Pulses (> " + highPercentile + " percentile) [cfs]";
statsSummaryTable[40][0] = "Average Duration of High Pulses (> " + highPercentile + " percentile) [days]";
statsSummaryTable[41][0] = "Number of Low Pulses (< " + lowPercentile + " percentile)";
statsSummaryTable[42][0] = "Threshold for Low Pulses (< " + lowPercentile + " percentile) [cfs]";
statsSummaryTable[43][0] = "Average Duration of Low Pulses (< " + lowPercentile + " percentile) [days]";
statsSummaryTable[44][0] = "Average Positive Difference Between Consecutive Days [cfs]";
statsSummaryTable[45][0] = "Average Negative Difference Between Consecutive Days [cfs]";
statsSummaryTable[46][0] = "Temporal Centriod of Discharge [Day of Calendary Year]";
statsSummaryTable[47][0] = "Richards-Baker Flow 'Flashiness' Index";
statsSummaryTable[48][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Count)";
statsSummaryTable[49][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Maximum) [cfs]";
statsSummaryTable[50][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Minimum) [cfs]";
statsSummaryTable[51][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Upper Quartile) [cfs]";
statsSummaryTable[52][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Lower Quartile) [cfs]";
statsSummaryTable[53][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Median) [cfs]";
statsSummaryTable[54][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Mean) [cfs]";
statsSummaryTable[55][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Standard Deviation) [cfs]";
statsSummaryTable[56][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Variance)";
statsSummaryTable[57][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Skewness)";
statsSummaryTable[58][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Coefficient of Variation)";
if(showMonthlyTF){
statsSummaryTable[59][0] = "January (Count)";
statsSummaryTable[60][0] = "January (Maximum) [cfs]";
statsSummaryTable[61][0] = "January (Minimum) [cfs]";
statsSummaryTable[62][0] = "January (Upper Quartile) [cfs]";
statsSummaryTable[63][0] = "January (Lower Quartile) [cfs]";
statsSummaryTable[64][0] = "January (Median) [cfs]";
statsSummaryTable[65][0] = "January (Mean) [cfs]";
statsSummaryTable[66][0] = "January (Standard Deviation) [cfs]";
statsSummaryTable[67][0] = "January (Variance)";
statsSummaryTable[68][0] = "January (Skewness)";
statsSummaryTable[69][0] = "January (Coefficient of Variation)";
statsSummaryTable[70][0] = "February (Count)";
statsSummaryTable[71][0] = "February (Maximum) [cfs]";
statsSummaryTable[72][0] = "February (Minimum) [cfs]";
statsSummaryTable[73][0] = "February (Upper Quartile) [cfs]";
statsSummaryTable[74][0] = "February (Lower Quartile) [cfs]";
statsSummaryTable[75][0] = "February (Median) [cfs]";
statsSummaryTable[76][0] = "February (Mean) [cfs]";
statsSummaryTable[77][0] = "February (Standard Deviation) [cfs]";
statsSummaryTable[78][0] = "February (Variance)";
statsSummaryTable[79][0] = "February (Skewness)";
statsSummaryTable[80][0] = "February (Coefficient of Variation)";
statsSummaryTable[81][0] = "March (Count)";
statsSummaryTable[82][0] = "March (Maximum) [cfs]";
statsSummaryTable[83][0] = "March (Minimum) [cfs]";
statsSummaryTable[84][0] = "March (Upper Quartile) [cfs]";
statsSummaryTable[85][0] = "March (Lower Quartile) [cfs]";
statsSummaryTable[86][0] = "March (Median) [cfs]";
statsSummaryTable[87][0] = "March (Mean) [cfs]";
statsSummaryTable[88][0] = "March (Standard Deviation) [cfs]";
statsSummaryTable[89][0] = "March (Variance)";
statsSummaryTable[90][0] = "March (Skewness)";
statsSummaryTable[91][0] = "March (Coefficient of Variation)";
statsSummaryTable[92][0] = "April (Count)";
statsSummaryTable[93][0] = "April (Maximum) [cfs]";
statsSummaryTable[94][0] = "April (Minimum) [cfs]";
statsSummaryTable[95][0] = "April (Upper Quartile) [cfs]";
statsSummaryTable[96][0] = "April (Lower Quartile) [cfs]";
statsSummaryTable[97][0] = "April (Median) [cfs]";
statsSummaryTable[98][0] = "April (Mean) [cfs]";
statsSummaryTable[99][0] = "April (Standard Deviation) [cfs]";
statsSummaryTable[100][0] = "April (Variance)";
statsSummaryTable[101][0] = "April (Skewness)";
statsSummaryTable[102][0] = "April (Coefficient of Variation)";
statsSummaryTable[103][0] = "May (Count)";
statsSummaryTable[104][0] = "May (Maximum) [cfs]";
statsSummaryTable[105][0] = "May (Minimum) [cfs]";
statsSummaryTable[106][0] = "May (Upper Quartile) [cfs]";
statsSummaryTable[107][0] = "May (Lower Quartile) [cfs]";
statsSummaryTable[108][0] = "May (Median) [cfs]";
statsSummaryTable[109][0] = "May (Mean) [cfs]";
statsSummaryTable[110][0] = "May (Standard Deviation) [cfs]";
statsSummaryTable[111][0] = "May (Variance)";
statsSummaryTable[112][0] = "May (Skewness)";
statsSummaryTable[113][0] = "May (Coefficient of Variation)";
statsSummaryTable[114][0] = "June (Count)";
statsSummaryTable[115][0] = "June (Maximum) [cfs]";
statsSummaryTable[116][0] = "June (Minimum) [cfs]";
statsSummaryTable[117][0] = "June (Upper Quartile) [cfs]";
statsSummaryTable[118][0] = "June (Lower Quartile) [cfs]";
statsSummaryTable[119][0] = "June (Median) [cfs]";
statsSummaryTable[120][0] = "June (Mean) [cfs]";
statsSummaryTable[121][0] = "June (Standard Deviation) [cfs]";
statsSummaryTable[122][0] = "June (Variance)";
statsSummaryTable[123][0] = "June (Skewness)";
statsSummaryTable[124][0] = "June (Coefficient of Variation)";
statsSummaryTable[125][0] = "July (Count)";
statsSummaryTable[126][0] = "July (Maximum) [cfs]";
statsSummaryTable[127][0] = "July (Minimum) [cfs]";
statsSummaryTable[128][0] = "July (Upper Quartile) [cfs]";
statsSummaryTable[129][0] = "July (Lower Quartile) [cfs]";
statsSummaryTable[130][0] = "July (Median) [cfs]";
statsSummaryTable[131][0] = "July (Mean) [cfs]";
statsSummaryTable[132][0] = "July (Standard Deviation) [cfs]";
statsSummaryTable[133][0] = "July (Variance)";
statsSummaryTable[134][0] = "July (Skewness)";
statsSummaryTable[135][0] = "July (Coefficient of Variation)";
statsSummaryTable[136][0] = "August (Count)";
statsSummaryTable[137][0] = "August (Maximum) [cfs]";
statsSummaryTable[138][0] = "August (Minimum) [cfs]";
statsSummaryTable[139][0] = "August (Upper Quartile) [cfs]";
statsSummaryTable[140][0] = "August (Lower Quartile) [cfs]";
statsSummaryTable[141][0] = "August (Median) [cfs]";
statsSummaryTable[142][0] = "August (Mean) [cfs]";
statsSummaryTable[143][0] = "August (Standard Deviation) [cfs]";
statsSummaryTable[144][0] = "August (Variance)";
statsSummaryTable[145][0] = "August (Skewness)";
statsSummaryTable[146][0] = "August (Coefficient of Variation)";
statsSummaryTable[147][0] = "September (Count)";
statsSummaryTable[148][0] = "September (Maximum) [cfs]";
statsSummaryTable[149][0] = "September (Minimum) [cfs]";
statsSummaryTable[150][0] = "September (Upper Quartile) [cfs]";
statsSummaryTable[151][0] = "September (Lower Quartile) [cfs]";
statsSummaryTable[152][0] = "September (Median) [cfs]";
statsSummaryTable[153][0] = "September (Mean) [cfs]";
statsSummaryTable[154][0] = "September (Standard Deviation) [cfs]";
statsSummaryTable[155][0] = "September (Variance)";
statsSummaryTable[156][0] = "September (Skewness)";
statsSummaryTable[157][0] = "September (Coefficient of Variation)";
statsSummaryTable[158][0] = "October (Count)";
statsSummaryTable[159][0] = "October (Maximum) [cfs]";
statsSummaryTable[160][0] = "October (Minimum) [cfs]";
statsSummaryTable[161][0] = "October (Upper Quartile) [cfs]";
statsSummaryTable[162][0] = "October (Lower Quartile) [cfs]";
statsSummaryTable[163][0] = "October (Median) [cfs]";
statsSummaryTable[164][0] = "October (Mean) [cfs]";
statsSummaryTable[165][0] = "October (Standard Deviation) [cfs]";
statsSummaryTable[166][0] = "October (Variance)";
statsSummaryTable[167][0] = "October (Skewness)";
statsSummaryTable[168][0] = "October (Coefficient of Variation)";
statsSummaryTable[169][0] = "November (Count)";
statsSummaryTable[170][0] = "November (Maximum) [cfs]";
statsSummaryTable[171][0] = "November (Minimum) [cfs]";
statsSummaryTable[172][0] = "November (Upper Quartile) [cfs]";
statsSummaryTable[173][0] = "November (Lower Quartile) [cfs]";
statsSummaryTable[174][0] = "November (Median) [cfs]";
statsSummaryTable[175][0] = "November (Mean) [cfs]";
statsSummaryTable[176][0] = "November (Standard Deviation) [cfs]";
statsSummaryTable[177][0] = "November (Variance)";
statsSummaryTable[178][0] = "November (Skewness)";
statsSummaryTable[179][0] = "November (Coefficient of Variation)";
statsSummaryTable[180][0] = "December (Count)";
statsSummaryTable[181][0] = "December (Maximum) [cfs]";
statsSummaryTable[182][0] = "December (Minimum) [cfs]";
statsSummaryTable[183][0] = "December (Upper Quartile) [cfs]";
statsSummaryTable[184][0] = "December (Lower Quartile) [cfs]";
statsSummaryTable[185][0] = "December (Median) [cfs]";
statsSummaryTable[186][0] = "December (Mean) [cfs]";
statsSummaryTable[187][0] = "December (Standard Deviation) [cfs]";
statsSummaryTable[188][0] = "December (Variance)";
statsSummaryTable[189][0] = "December (Skewness)";
statsSummaryTable[190][0] = "December (Coefficient of Variation)";
}
statsSummaryTable[statsSummaryTable.length - 6][0] = "Flow Statistics based on Indicators of Hydrologic Alteration from:";
statsSummaryTable[statsSummaryTable.length - 5][0] = "B.D. Richter; J.V. Baumgartner; J. Powell; D.P. Braun. 1996. 'A Method For Assessing Hydrologic Aleration Within Ecosystems.' Conservation Biology 10(4): 1163-1174.";
statsSummaryTable[statsSummaryTable.length - 4][0] = "B.D. Richter; J.V. Baumgartner; R. Wigington; D.P Braun. 1997. 'How Much Water Does A River Need?' Freshwater Biology. 37: 231-249.";
statsSummaryTable[statsSummaryTable.length - 3][0] = "B.D. Richter; J.V. Baumgartner; D.P. Braun; J. Powell. 1998. 'A Spatial Assessment Of Hydrologic Alteration Within A River Network.' Regul. Rivers: Res. Mgmt. 14: 329-340.";
statsSummaryTable[statsSummaryTable.length - 2][0] = "Richards-Baker Flashiness Index from:";
statsSummaryTable[statsSummaryTable.length - 1][0] = "D.B. Baker; R.P. Richards; T.T. Loftus; J.W. Kramer. 2004. 'A New Flashiness Index: Characteristics and Applications to Midwestern Rivers and Streams.' Journal of the Americal Water Resources Association (JAWRA) April 2004: 503-522.";
//Calculate all data statistics
//These are not annual statistics because the data can include more than 1 year
String beginDate = flowData[0][0];
String endDate = flowData[flowData.length - 1][0];
Object[] resultArray = calculateFlowStatistics(statsSummaryTable,
flowData,
highPercentile,
lowPercentile,
m,
"All: " + beginDate + " to " + endDate,
showMonthlyTF,
seasonBegin,
seasonEnd);
statsSummaryTable = (String[][]) resultArray[0];
//double mQnFlow_temp = (Double) resultArray[1];
//Calculate data statistics for each period
Arrays.sort(flowData, new DateComparator());
SimpleDateFormat desiredDateFormat = new SimpleDateFormat("yyyy-MM-dd");
if(!period1Begin.isEmpty() && !period1End.isEmpty()){
Date period1Begin_date = desiredDateFormat.parse(period1Begin);
Date period1End_date = desiredDateFormat.parse(period1End);
ArrayList<String> period1_dates = new ArrayList<>();
ArrayList<String> period1_flows = new ArrayList<>();
for(int i=0; i<flowData.length; i++){
Date newDate = desiredDateFormat.parse(flowData[i][0]);
if(newDate.compareTo(period1Begin_date) >= 0 && newDate.compareTo(period1End_date) <= 0){
period1_dates.add(flowData[i][0]);
period1_flows.add(flowData[i][1]);
}
}
String[][] period1Data = new String[period1_dates.size()][2];
for(int i=0; i<period1_dates.size(); i++){
period1Data[i][0] = period1_dates.get(i);
period1Data[i][1] = period1_flows.get(i);
}
//Calculate Period 1 data statistics
resultArray = calculateFlowStatistics(statsSummaryTable,
period1Data,
highPercentile,
lowPercentile,
m,
"Period-1 Data: " + period1Begin + " to " + period1End,
showMonthlyTF,
seasonBegin,
seasonEnd);
statsSummaryTable = (String[][]) resultArray[0];
//double mQnFlow_temp = (Double) resultArray[1];
}
if(!period2Begin.isEmpty() && !period2End.isEmpty()){
Date period2Begin_date = desiredDateFormat.parse(period2Begin);
Date period2End_date = desiredDateFormat.parse(period2End);
ArrayList<String> period2_dates = new ArrayList<>();
ArrayList<String> period2_flows = new ArrayList<>();
for(int i=0; i<flowData.length; i++){
Date newDate = desiredDateFormat.parse(flowData[i][0]);
if(newDate.compareTo(period2Begin_date) >= 0 && newDate.compareTo(period2End_date) <= 0){
period2_dates.add(flowData[i][0]);
period2_flows.add(flowData[i][1]);
}
}
String[][] period2Data = new String[period2_dates.size()][2];
for(int i=0; i<period2_dates.size(); i++){
period2Data[i][0] = period2_dates.get(i);
period2Data[i][1] = period2_flows.get(i);
}
//Calculate Period 2 data statistics
resultArray = calculateFlowStatistics(statsSummaryTable,
period2Data,
highPercentile,
lowPercentile,
m,
"Period-2 Data: " + period2Begin + " to " + period2End,
showMonthlyTF,
seasonBegin,
seasonEnd);
statsSummaryTable = (String[][]) resultArray[0];
//double mQnFlow_temp = (Double) resultArray[1];
}
if(!period3Begin.isEmpty() && !period3End.isEmpty()){
Date period3Begin_date = desiredDateFormat.parse(period3Begin);
Date period3End_date = desiredDateFormat.parse(period3End);
ArrayList<String> period3_dates = new ArrayList<>();
ArrayList<String> period3_flows = new ArrayList<>();
for(int i=0; i<flowData.length; i++){
Date newDate = desiredDateFormat.parse(flowData[i][0]);
if(newDate.compareTo(period3Begin_date) >= 0 && newDate.compareTo(period3End_date) <= 0){
period3_dates.add(flowData[i][0]);
period3_flows.add(flowData[i][1]);
}
}
String[][] period3Data = new String[period3_dates.size()][2];
for(int i=0; i<period3_dates.size(); i++){
period3Data[i][0] = period3_dates.get(i);
period3Data[i][1] = period3_flows.get(i);
}
//Calculate Period 3 data statistics
resultArray = calculateFlowStatistics(statsSummaryTable,
period3Data,
highPercentile,
lowPercentile,
m,
"Period-3 Data: " + period3Begin + " to " + period3End,
showMonthlyTF,
seasonBegin,
seasonEnd);
statsSummaryTable = (String[][]) resultArray[0];
//double mQnFlow_temp = (Double) resultArray[1];
}
//Calculate Flow statistics for each year in time period
ArrayList<Double> mQnFlows = new ArrayList<>();
boolean moreYears = flowData.length > 0;
String currentYear = flowData[0][0].substring(0,4);
String finalYear = flowData[flowData.length - 1][0].substring(0,4);
if(waterYearTF){
Date firstDate = desiredDateFormat.parse(flowData[0][0]);
Date firstWaterYear = desiredDateFormat.parse(currentYear + "-10-01");
if(firstDate.before(firstWaterYear)){
currentYear = String.valueOf(Integer.parseInt(currentYear) - 1);
}
Date lastDate = desiredDateFormat.parse(flowData[flowData.length - 1][0]);
Date lastWaterYear = desiredDateFormat.parse(finalYear + "-09-30");
if(lastDate.after(lastWaterYear)){
finalYear = String.valueOf(Integer.parseInt(finalYear) + 1);
}
}
while(moreYears){
String[][] partialData = new String[0][2];
String headerLabel = null;
if(waterYearTF){
//Get current water year's data
String nextYear = String.valueOf(Integer.parseInt(currentYear) + 1);
partialData = DoubleArray.getPeriodData(flowData, currentYear + "-10-01", nextYear + "-09-30");
headerLabel = "Water Year: " + currentYear + "-10-01 to " + nextYear + "-09-30";
}else{
//Get current calendar year's data
partialData = DoubleArray.getYearsData(flowData, currentYear);
headerLabel = currentYear;
}
resultArray = calculateFlowStatistics(statsSummaryTable,
partialData,
highPercentile,
lowPercentile,
m,
headerLabel,
showMonthlyTF,
seasonBegin,
seasonEnd);
statsSummaryTable = (String[][]) resultArray[0];
double mQnFlow_temp = (Double) resultArray[1];
mQnFlows.add(mQnFlow_temp);
if(waterYearTF){
//For water year comparison
String nextYear = String.valueOf(Integer.parseInt(currentYear) + 1);
Date currentEndDate = desiredDateFormat.parse(nextYear + "-09-30");
Date lastWaterYear = desiredDateFormat.parse(finalYear + "-09-30");
if(currentEndDate.before(lastWaterYear)){
currentYear = String.valueOf(nextYear);
}else{
moreYears = false;
}
}else{
//For calendar year comparison
int nextYear = Integer.parseInt(currentYear) + 1;
if(finalYear.compareToIgnoreCase(String.valueOf(nextYear)) >= 0){
currentYear = String.valueOf(nextYear);
}else{
moreYears = false;
}
}
}
//Call file writer for outputs fo flow statistics
writeStatsSummaryFile(directory, statsSummaryTable);
//Calculate mQn (7Q10) Statistics
double mQnFlow = 0;
if(m != 0 && n != 0){
mQnFlow = DoubleArray.calculateLowFlowReturnPeriod(mQnFlows, n);
}
String errorMessage = "";
if(mQnFlow == 0){
errorMessage = "There is no " + m + " consecutive day flow recorded so there was no " + m + "Q" + n + " flow value calculated.";
}
Object[] returnArray = {mQnFlow, errorMessage};
return returnArray;
}
/**
* calculates flow statistics for the provided flowData and appends the column
* of results to the statsSummaryTable under the provided header
* @param statsSummaryTable the summary table to add to
* @param flowData the String[][] containing sorted data for the time series
* (column 1 = dates (yyyy-mm-dd) column 2 = value
* @param highPercentile the percentile value for defining a "high flow pulse"
* typically greater than 75% = 0.75
* @param lowPercentile the percentile value for defining a "low flow pulse"
* typically lower than 25% = 0.25
* @param m used to calculate an "m-day-average" minimum flow
* @param dataHeader the text header for this column of result statistics in the statsSummaryTable (i.e. 'All Data' or '1998')
* @param showMonthlyTF a flag to show monthly statistics (min/max/etc.) or not (which reduces the total output of the tool)
* @param seasonBegin the month and date (MM-dd) of the start of a seasonal analysis to be averaged over the period of data
* @param seasonEnd the month and date (MM-dd) of the end of a seasonal analysis to be averaged over the period of data
* @return an Object[] containing the updated statsSummaryTable at returnObject[0] and the m-day-average minimum-flow at returnObject[1]
* @throws IOException
*/
private Object[] calculateFlowStatistics(String[][] statsSummaryTable,
String[][] flowData,
double highPercentile,
double lowPercentile,
int m,
String dataHeader,
boolean showMonthlyTF,
String seasonBegin,
String seasonEnd) throws IOException, ParseException{
//Sort Data consecutively
Arrays.sort(flowData, new DateComparator());
//Get/Store data for seasonal statistics
String[][] seasonalData_String = DoubleArray.getSeasonalData(flowData, seasonBegin, seasonEnd);
ArrayList<Double> seasonal_data = new ArrayList<>();
for(int i=0; i<seasonalData_String.length; i++){
double value = Double.parseDouble(seasonalData_String[i][1]);
seasonal_data.add(value);
}
//Set up monthly data arrays
ArrayList<Double> jan_data = new ArrayList<>();
ArrayList<Double> feb_data = new ArrayList<>();
ArrayList<Double> mar_data = new ArrayList<>();
ArrayList<Double> apr_data = new ArrayList<>();
ArrayList<Double> may_data = new ArrayList<>();
ArrayList<Double> jun_data = new ArrayList<>();
ArrayList<Double> jul_data = new ArrayList<>();
ArrayList<Double> aug_data = new ArrayList<>();
ArrayList<Double> sep_data = new ArrayList<>();
ArrayList<Double> oct_data = new ArrayList<>();
ArrayList<Double> nov_data = new ArrayList<>();
ArrayList<Double> dec_data = new ArrayList<>();
//Calculate 1-day statistics
ArrayList<Double> allData = new ArrayList<>();
ArrayList<Double> diffPositive = new ArrayList<>();
ArrayList<Double> diffNegative = new ArrayList<>();
double max_1day = -9999, min_1day = 9999;
String max_1day_date = "-1", min_1day_date = "-1";
boolean increasing = false;
int ctr_zero = 0, ctr_rises = 0, ctr_falls = 0, ctr_reversals = 0;
String date1 = "1900-01-01";
double centroidSum = 0, sum = 0, RBI_numerator = 0, oldValue = 0;
for(int i=0; i<flowData.length; i++){
String date2 = flowData[i][0];
double month = Double.parseDouble(flowData[i][0].substring(5,7));
int month_int = (int) month;
double value = Double.parseDouble(flowData[i][1]);
allData.add(value);
//Store data for monthly averages
switch (month_int) {
case 1: jan_data.add(value); break;
case 2: feb_data.add(value); break;
case 3: mar_data.add(value); break;
case 4: apr_data.add(value); break;
case 5: may_data.add(value); break;
case 6: jun_data.add(value); break;
case 7: jul_data.add(value); break;
case 8: aug_data.add(value); break;
case 9: sep_data.add(value); break;
case 10: oct_data.add(value); break;
case 11: nov_data.add(value); break;
case 12: dec_data.add(value); break;
default: break;
}
//Calculate Max
if(max_1day < value){
max_1day_date = date2;
max_1day = value;
}
//Calculate Min
if(min_1day > value){
min_1day_date = date2;
min_1day = value;
}
//Count zero flow days
if(value == 0){
ctr_zero++;
}
//Calculate difference
if(DoubleArray.checkSubsequentDates(date1, date2)){
double diff = value - oldValue;
if(diff > 0){
diffPositive.add(diff);
}else if(diff < 0){
diffNegative.add(diff);
}
RBI_numerator = RBI_numerator + Math.abs(diff);
//Calculate flow reversals for consecutive days only
if(value > oldValue){
if(!increasing){
ctr_reversals++;
ctr_rises++;
increasing = true;
}
}else if(value < oldValue){
if(increasing){
ctr_reversals++;
ctr_falls++;
increasing = false;
}
}
}
//Calculate centriod values
double currentYear_double = Double.parseDouble(flowData[i][0].substring(0,4));
double currentMonth_double = Double.parseDouble(flowData[i][0].substring(5,7));
double currentDay_double = Double.parseDouble(flowData[i][0].substring(8));
int currentYear = (int) currentYear_double;
int currentMonth = (int) currentMonth_double;
int currentDay = (int) currentDay_double;
Calendar currentDate = new GregorianCalendar(currentYear, currentMonth - 1, currentDay);
int dayOfYear = currentDate.get(Calendar.DAY_OF_YEAR);
centroidSum = centroidSum + dayOfYear * value;
sum = sum + value;
//Reset yesterday's flow value
oldValue = value;
date1 = date2;
}
double mean_all = DoubleMath.meanArithmetic(allData);
double centroid = centroidSum / sum;
double flashinessIndex = RBI_numerator / sum;
//Calculate 3-day statistics
Object[] resultArray = DoubleArray.getMdayData(flowData, 3, "arithmetic");
ArrayList<String> average_3day_date = (ArrayList<String>) resultArray[0];
ArrayList<Double> average_3day = (ArrayList<Double>) resultArray[1];
double max_3day = DoubleMath.max(average_3day);
double min_3day = DoubleMath.min(average_3day);
String max_3day_date = getDateOfValue(average_3day_date, average_3day, max_3day);
String min_3day_date = getDateOfValue(average_3day_date, average_3day, min_3day);
//Calculate 7-day statistics
resultArray = DoubleArray.getMdayData(flowData, 7, "arithmetic");
ArrayList<String> average_7day_date = (ArrayList<String>) resultArray[0];
ArrayList<Double> average_7day = (ArrayList<Double>) resultArray[1];
double max_7day = DoubleMath.max(average_7day);
double min_7day = DoubleMath.min(average_7day);
double min_7day_ave = DoubleMath.round(min_7day/mean_all,3);
String max_7day_date = getDateOfValue(average_7day_date, average_7day, max_7day);
String min_7day_date = getDateOfValue(average_7day_date, average_7day, min_7day);
//Calculate 30-day statistics
resultArray = DoubleArray.getMdayData(flowData, 30, "arithmetic");
ArrayList<String> average_30day_date = (ArrayList<String>) resultArray[0];
ArrayList<Double> average_30day = (ArrayList<Double>) resultArray[1];
double max_30day = DoubleMath.max(average_30day);
double min_30day = DoubleMath.min(average_30day);
String max_30day_date = getDateOfValue(average_30day_date, average_30day, max_30day);
String min_30day_date = getDateOfValue(average_30day_date, average_30day, min_30day);
//Calculate 90-day statistics
resultArray = DoubleArray.getMdayData(flowData, 90, "arithmetic");
ArrayList<String> average_90day_date = (ArrayList<String>) resultArray[0];
ArrayList<Double> average_90day = (ArrayList<Double>) resultArray[1];
double max_90day = DoubleMath.max(average_90day);
double min_90day = DoubleMath.min(average_90day);
String max_90day_date = getDateOfValue(average_90day_date, average_90day, max_90day);
String min_90day_date = getDateOfValue(average_90day_date, average_90day, min_90day);
//Calculate mQn (7Q10)Statistics
double min_Mday = 0;
if(m != 0){
resultArray = DoubleArray.getMdayData(flowData, m, "arithmetic");
ArrayList<Double> average_Mday = (ArrayList<Double>) resultArray[1];
min_Mday = DoubleMath.min(average_Mday);
}
//Calculate Pulse Information
double highLimit = DoubleMath.Percentile_function(allData, highPercentile);
double lowLimit = DoubleMath.Percentile_function(allData, lowPercentile);
ArrayList<Double> highPulses = new ArrayList<>();
ArrayList<Double> lowPulses = new ArrayList<>();
int ctr_highPulse = 0, ctr_lowPulse = 0;
double duration_highPulse = 0, duration_lowPulse = 0;
boolean highPulseTF = false, lowPulseTF = false;
oldValue = 0;
date1 = "";
for(int i=0; i<flowData.length; i++){
//Check for consecutive days of flow data
String date2 = flowData[i][0];
double value = Double.parseDouble(flowData[i][1]);
if(i>0){
if(DoubleArray.checkSubsequentDates(date1, date2)){
//Calculate high pulse information
if(value > highLimit){
if(!highPulseTF){
//If on a new highPulse add to the counters
ctr_highPulse++;
duration_highPulse = 1;
highPulseTF = true;
}else{
//If still on a highPulse, add to the counter
duration_highPulse = duration_highPulse + 1;
}
}else{
//If no longer on a highPulse, store the results, reset the counters
if(highPulseTF){
highPulses.add(duration_highPulse);
highPulseTF = false;
}
}
//Calculate low pulse information
if(value < lowLimit){
if(!lowPulseTF){
//If on a new lowPulse add to the counters
ctr_lowPulse++;
duration_lowPulse = 1;
lowPulseTF = true;
}else{
//If still on a lowPulse, add to the counter
duration_lowPulse = duration_lowPulse + 1;
}
}else{
//If no longer on a lowPulse, store the results, reset the counters
if(lowPulseTF){
lowPulses.add(duration_lowPulse);
lowPulseTF = false;
}
}
}else{
//If no longer on consecutive days, store the results (if previously on a pulse) and reset the counters
if(highPulseTF){
highPulses.add(duration_highPulse);
highPulseTF = false;
}
if(lowPulseTF){
lowPulses.add(duration_lowPulse);
lowPulseTF = false;
}
}
}
//Reset yesterday's flow value
oldValue = value;
date1 = date2;
}
//Build Flow Statistics Summary for output
int summarySize = 65;
if(showMonthlyTF) summarySize = 197;
String[] additionalSummary = new String[summarySize];
additionalSummary[0] = "";//blank
additionalSummary[1] = dataHeader;//Method
additionalSummary[2] = String.valueOf(allData.size());//Count, overall
additionalSummary[3] = String.valueOf(DoubleMath.round(DoubleMath.max(allData), 3));//Maximum, overall
additionalSummary[4] = String.valueOf(DoubleMath.round(DoubleMath.min(allData), 3));//Minimum, overall
additionalSummary[5] = String.valueOf(DoubleMath.round(DoubleMath.Percentile_function(allData,0.75), 3));//Upper Quartile, overall
additionalSummary[6] = String.valueOf(DoubleMath.round(DoubleMath.Percentile_function(allData,0.25), 3));//Lower Quartile, overall
additionalSummary[7] = String.valueOf(DoubleMath.round(DoubleMath.median(allData), 3));//Median, overall
additionalSummary[8] = String.valueOf(DoubleMath.round(DoubleMath.meanArithmetic(allData), 3));//Average, overall
additionalSummary[9] = String.valueOf(DoubleMath.round(DoubleMath.StandardDeviationSample(allData), 3));//Standard Deviation, overall
additionalSummary[10] = String.valueOf(DoubleMath.round(DoubleMath.VarianceSample(allData), 3));//Variance, overall
additionalSummary[11] = String.valueOf(DoubleMath.round(DoubleMath.SkewnessSample(allData), 3));//Skewness, overall
additionalSummary[12] = String.valueOf(DoubleMath.round(DoubleMath.CoefficientOfVariation(allData), 3));//Coefficient of Variation, overall
additionalSummary[13] = String.valueOf(DoubleMath.round(max_1day,3));//Maximum (1-day)
additionalSummary[14] = max_1day_date;//Date of Maximum (1-day)
additionalSummary[15] = String.valueOf(DoubleMath.round(min_1day,3));//Minimum (1-day)
additionalSummary[16] = min_1day_date;//Date of Minimum (1-day)
additionalSummary[17] = String.valueOf(DoubleMath.round(max_3day,3));//Maximum (3-day)
additionalSummary[18] = max_3day_date;//Dates of Maximum (3-day)
additionalSummary[19] = String.valueOf(DoubleMath.round(min_3day,3));//Minimum (3-day)
additionalSummary[20] = min_3day_date;//Dates of Minimum (3-day)
additionalSummary[21] = String.valueOf(DoubleMath.round(max_7day,3));//Maximum (7-day)
additionalSummary[22] = max_7day_date;//Dates of Maximum (7-day)
additionalSummary[23] = String.valueOf(DoubleMath.round(min_7day,3));//Minimum (7-day)
additionalSummary[24] = min_7day_date;//Dates of Minimum (7-day)
additionalSummary[25] = String.valueOf(DoubleMath.round(min_7day_ave,3));//Minimum (7-day)
additionalSummary[26] = String.valueOf(DoubleMath.round(max_30day,3));//Maximum (30-day)
additionalSummary[27] = max_30day_date;//Dates of Maximum (30-day)
additionalSummary[28] = String.valueOf(DoubleMath.round(min_30day,3));//Minimum (30-day)
additionalSummary[29] = min_30day_date;//Dates of Minimum (30-day)
additionalSummary[30] = String.valueOf(DoubleMath.round(max_90day,3));//Maximum (90-day)
additionalSummary[31] = max_90day_date;//Dates of Maximum (90-day)
additionalSummary[32] = String.valueOf(DoubleMath.round(min_90day,3));//Minimum (90-day)
additionalSummary[33] = min_90day_date;//Dates of Minimum (90-day)
additionalSummary[34] = String.valueOf(ctr_zero);//Number of Zero Flow Days
additionalSummary[35] = String.valueOf(ctr_reversals);//Number of Flow Reversals
additionalSummary[36] = String.valueOf(ctr_rises);//Number of Flow Rises
additionalSummary[37] = String.valueOf(ctr_falls);//Number of Flow Falls
additionalSummary[38] = String.valueOf(ctr_highPulse);//Number of High Pulses
additionalSummary[39] = String.valueOf(DoubleMath.round(highLimit,1));//Threshold for High Pulses
additionalSummary[40] = String.valueOf(DoubleMath.round(DoubleMath.meanArithmetic(highPulses), 3));//Average Duration of High Pulses
additionalSummary[41] = String.valueOf(ctr_lowPulse);//Number of Low Pulses
additionalSummary[42] = String.valueOf(DoubleMath.round(lowLimit,1));//Threshold for Low Pulses
additionalSummary[43] = String.valueOf(DoubleMath.round(DoubleMath.meanArithmetic(lowPulses), 3));//Average Duration of Low Pulses
additionalSummary[44] = String.valueOf(DoubleMath.round(DoubleMath.meanArithmetic(diffPositive),3));//Average Positive Difference Between Consecutive Days
additionalSummary[45] = String.valueOf(DoubleMath.round(DoubleMath.meanArithmetic(diffNegative),3));//Average Negative Difference Between Consecutive Days
additionalSummary[46] = String.valueOf(DoubleMath.round(centroid,2));//Temporal centroid of annual discharge (Julian day, not water-year day)
additionalSummary[47] = String.valueOf(DoubleMath.round(flashinessIndex,4));//Richards-Baker Index for flow 'flashiness'
//Add seasonal stats summary
int index = 48;
resultArray = addSimpleStatsSummary(additionalSummary, seasonal_data, index);
additionalSummary = (String[]) resultArray[0];
index = (int) resultArray[1];
if(showMonthlyTF){
//January
resultArray = addSimpleStatsSummary(additionalSummary, jan_data, index);
additionalSummary = (String[]) resultArray[0];
index = (int) resultArray[1];
//February
resultArray = addSimpleStatsSummary(additionalSummary, feb_data, index);
additionalSummary = (String[]) resultArray[0];
index = (int) resultArray[1];
//March
resultArray = addSimpleStatsSummary(additionalSummary, mar_data, index);
additionalSummary = (String[]) resultArray[0];
index = (int) resultArray[1];
//April
resultArray = addSimpleStatsSummary(additionalSummary, apr_data, index);
additionalSummary = (String[]) resultArray[0];
index = (int) resultArray[1];
//May
resultArray = addSimpleStatsSummary(additionalSummary, may_data, index);
additionalSummary = (String[]) resultArray[0];
index = (int) resultArray[1];
//June
resultArray = addSimpleStatsSummary(additionalSummary, jun_data, index);
additionalSummary = (String[]) resultArray[0];
index = (int) resultArray[1];
//July
resultArray = addSimpleStatsSummary(additionalSummary, jul_data, index);
additionalSummary = (String[]) resultArray[0];
index = (int) resultArray[1];
//August
resultArray = addSimpleStatsSummary(additionalSummary, aug_data, index);
additionalSummary = (String[]) resultArray[0];
index = (int) resultArray[1];
//September
resultArray = addSimpleStatsSummary(additionalSummary, sep_data, index);
additionalSummary = (String[]) resultArray[0];
index = (int) resultArray[1];
//October
resultArray = addSimpleStatsSummary(additionalSummary, oct_data, index);
additionalSummary = (String[]) resultArray[0];
index = (int) resultArray[1];
//November
resultArray = addSimpleStatsSummary(additionalSummary, nov_data, index);
additionalSummary = (String[]) resultArray[0];
index = (int) resultArray[1];
//December
resultArray = addSimpleStatsSummary(additionalSummary, dec_data, index);
additionalSummary = (String[]) resultArray[0];
index = (int) resultArray[1];
}
//References section blanks
additionalSummary[index] = "";//blank
additionalSummary[index + 1] = "";//blank
additionalSummary[index + 2] = "";//blank
additionalSummary[index + 3] = "";//blank
additionalSummary[index + 4] = "";//blank
additionalSummary[index + 5] = "";//blank
//Add these statistics to the existing results
statsSummaryTable = DoubleArray.appendcolumn_Matrix(statsSummaryTable, additionalSummary);
Object[] returnArray = {statsSummaryTable, min_Mday};
return returnArray;
}
/**
* Calculates a statistics summary of the provided data and appends it
* (beginning at 'index') to the provided statsSummaryTable.
*
* This calculates 11 parameters
* @param statsSummaryTable
* @param data
* @param index
* @return
*/
private Object[] addSimpleStatsSummary(String[] statsSummaryTable, ArrayList<Double> data, int index){
statsSummaryTable[index] = String.valueOf(data.size());//count
statsSummaryTable[index + 1] = String.valueOf(DoubleMath.round(DoubleMath.max(data), 3));//Maximum
statsSummaryTable[index + 2] = String.valueOf(DoubleMath.round(DoubleMath.min(data), 3));//Minimum
statsSummaryTable[index + 3] = String.valueOf(DoubleMath.round(DoubleMath.Percentile_function(data, 0.75), 3));//Upper Quartile
statsSummaryTable[index + 4] = String.valueOf(DoubleMath.round(DoubleMath.Percentile_function(data,0.25), 3));//Lower Quartile
statsSummaryTable[index + 5] = String.valueOf(DoubleMath.round(DoubleMath.median(data), 3));//Median
statsSummaryTable[index + 6] = String.valueOf(DoubleMath.round(DoubleMath.meanArithmetic(data), 3));//Average
statsSummaryTable[index + 7] = String.valueOf(DoubleMath.round(DoubleMath.StandardDeviationSample(data), 3));//Standard Deviation
statsSummaryTable[index + 8] = String.valueOf(DoubleMath.round(DoubleMath.VarianceSample(data), 3));//Variance
statsSummaryTable[index + 9] = String.valueOf(DoubleMath.round(DoubleMath.SkewnessSample(data), 3));//Skewness
statsSummaryTable[index + 10] = String.valueOf(DoubleMath.round(DoubleMath.CoefficientOfVariation(data), 3));//Coefficient of Variation
index = index + 11;
Object[] returnArray = {statsSummaryTable, index};
return returnArray;
}
/**
* Loop through the provided data array and gets the corresponding index to 'value' and returns that index from the dates array
* @param dates a list of dates
* @param data a list of values corresponding to the dates in the above list
* @param value a value
* @return the date corresponding to the provided value
*/
private String getDateOfValue(ArrayList<String> dates, ArrayList<Double> data, double value){
String dateOfValue = "null";
for(int i=0; i<dates.size(); i++){
if(data.get(i) == value){
dateOfValue = dates.get(i);
break;
}
}
return dateOfValue;
}
/**
* Writes the provided array to a CSV result file
* @param directory the folder location for the result file
* @param statsSummary the desired contents of the file
* @throws IOException
*/
public void writeStatsSummaryFile(String directory, String[][] statsSummary) throws IOException{
//Open a file writer for the summary of the flow statistcs per year
String path = directory + File.separator + getFlowStatistics_summary();
FileWriter newFile = new FileWriter(path, false);
PrintWriter writer = new PrintWriter(newFile);
for(int i=0; i<statsSummary.length; i++){
String currentLine = statsSummary[i][0];
for(int j=1; j<statsSummary[i].length; j++){
currentLine = currentLine + "," + statsSummary[i][j];
}
writer.printf("%s" + "\r\n", currentLine);
}
//Close file writer
newFile.close();
writer.close();
System.out.println("Text File located at:\t" + path);
}
}