FlowStatistics.java [src/java/cfa] Revision: c2e48e60977c5a4055113bdf9743acf5247d9cd1 Date: Fri Oct 16 13:50:27 MDT 2015
package 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: 29-May-2015
* @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 mainFolder 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
* @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 mainFolder,
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) 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] = "Average [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 Average [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 + " (Average) [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 (Average) [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 (Average) [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 (Average) [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 (Average) [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 (Average) [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 (Average) [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 (Average) [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 (Average) [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 (Average) [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 (Average) [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 (Average) [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 (Average) [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<String>();
ArrayList<String> period1_flows = new ArrayList<String>();
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<String>();
ArrayList<String> period2_flows = new ArrayList<String>();
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<String>();
ArrayList<String> period3_flows = new ArrayList<String>();
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
DoubleArray doubleArray = new DoubleArray();
ArrayList<Double> mQnFlows = new ArrayList<Double>();
boolean moreYears = flowData.length > 0;
String currentYear = flowData[0][0].substring(0,4);
String finalYear = flowData[flowData.length - 1][0].substring(0,4);
while(moreYears){
//Get current year's data and calculate it's statistics
String[][] partialData = doubleArray.getYearsData(flowData, currentYear);
resultArray = calculateFlowStatistics(statsSummaryTable,
partialData,
highPercentile,
lowPercentile,
m,
currentYear,
showMonthlyTF,
seasonBegin,
seasonEnd);
statsSummaryTable = (String[][]) resultArray[0];
double mQnFlow_temp = (Double) resultArray[1];
mQnFlows.add(mQnFlow_temp);
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(mainFolder, 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{
//Determine statistics
DoubleMath doubleMath = new DoubleMath();
DoubleArray doubleArray = new DoubleArray();
//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<Double>();
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<Double>();
ArrayList<Double> feb_data = new ArrayList<Double>();
ArrayList<Double> mar_data = new ArrayList<Double>();
ArrayList<Double> apr_data = new ArrayList<Double>();
ArrayList<Double> may_data = new ArrayList<Double>();
ArrayList<Double> jun_data = new ArrayList<Double>();
ArrayList<Double> jul_data = new ArrayList<Double>();
ArrayList<Double> aug_data = new ArrayList<Double>();
ArrayList<Double> sep_data = new ArrayList<Double>();
ArrayList<Double> oct_data = new ArrayList<Double>();
ArrayList<Double> nov_data = new ArrayList<Double>();
ArrayList<Double> dec_data = new ArrayList<Double>();
//Calculate 1-day statistics
ArrayList<Double> allData = new ArrayList<Double>();
ArrayList<Double> diffPositive = new ArrayList<Double>();
ArrayList<Double> diffNegative = new ArrayList<Double>();
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<Double>();
ArrayList<Double> lowPulses = new ArrayList<Double>();
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){
DoubleMath doubleMath = new DoubleMath();
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 mainFolder the folder location for the result file
* @param statsSummary the desired contents of the file
* @throws IOException
*/
public void writeStatsSummaryFile(String mainFolder, String[][] statsSummary) throws IOException{
//Open a file writer for the summary of the flow statistcs per year
String path = mainFolder + 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);
}
}