FlowStatistics.java [src/java/cfa] Revision: c7fc79905f0bf1aff62fa0efc51db41505bb44e1  Date: Mon Sep 15 16:14:34 MDT 2014
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: 15-September-2014
* @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 desiredDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String today = desiredDateFormat.format(currentDate);
        stationName = stationName.replace(",", "");
        
        //Initialize the summary result table
        int summarySize = 50;
        if(showMonthlyTF) summarySize = 170;
        
        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] = "Maximum (1-day) [cfs]";
        statsSummaryTable[3][0] = "Date of Maximum (1-day)";
        statsSummaryTable[4][0] = "Minimum (1-day) [cfs]";
        statsSummaryTable[5][0] = "Date of Minimum (1-day)";
        statsSummaryTable[6][0] = "Maximum (3-day) [cfs]";
        statsSummaryTable[7][0] = "Dates of Maximum (3-day)";
        statsSummaryTable[8][0] = "Minimum (3-day) [cfs]";
        statsSummaryTable[9][0] = "Dates of Minimum (3-day)";
        statsSummaryTable[10][0] = "Maximum (7-day) [cfs]";
        statsSummaryTable[11][0] = "Dates of Maximum (7-day)";
        statsSummaryTable[12][0] = "Minimum (7-day) [cfs]";
        statsSummaryTable[13][0] = "Dates of Minimum (7-day)";
        statsSummaryTable[14][0] = "Minimum (7-day) / Annual Average  [cfs]";
        statsSummaryTable[15][0] = "Maximum (30-day) [cfs]";
        statsSummaryTable[16][0] = "Dates of Maximum (30-day)";
        statsSummaryTable[17][0] = "Minimum (30-day) [cfs]";
        statsSummaryTable[18][0] = "Dates of Minimum (30-day)";
        statsSummaryTable[19][0] = "Maximum (90-day) [cfs]";
        statsSummaryTable[20][0] = "Dates of Maximum (90-day)";
        statsSummaryTable[21][0] = "Minimum (90-day) [cfs]";
        statsSummaryTable[22][0] = "Dates of Minimum (90-day)";
        statsSummaryTable[23][0] = "Number of Zero Flow Days";
        statsSummaryTable[24][0] = "Number of Flow Reversals";
        statsSummaryTable[25][0] = "Number of Flow Rises";
        statsSummaryTable[26][0] = "Number of Flow Falls";
        statsSummaryTable[27][0] = "Number of High Pulses (> " + highPercentile + " percentile)";
        statsSummaryTable[28][0] = "Threshold for High Pulses (> " + highPercentile + " percentile) [cfs]";
        statsSummaryTable[29][0] = "Average Duration of High Pulses (> " + highPercentile + " percentile) [days]";
        statsSummaryTable[30][0] = "Number of Low Pulses (< " + lowPercentile + " percentile)";
        statsSummaryTable[31][0] = "Threshold for Low Pulses (< " + lowPercentile + " percentile) [cfs]";
        statsSummaryTable[32][0] = "Average Duration of Low Pulses (< " + lowPercentile + " percentile) [days]";
        statsSummaryTable[33][0] = "Average Positive Difference Between Consecutive Days [cfs]";
        statsSummaryTable[34][0] = "Average Negative Difference Between Consecutive Days [cfs]";
        statsSummaryTable[35][0] = "Temporal Centriod of Discharge [Day of Calendary Year]";
        statsSummaryTable[36][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Maximum) [cfs]";
        statsSummaryTable[37][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Minimum) [cfs]";
        statsSummaryTable[38][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Upper Quartile) [cfs]";
        statsSummaryTable[39][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Lower Quartile) [cfs]";
        statsSummaryTable[40][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Median) [cfs]";
        statsSummaryTable[41][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Average) [cfs]";
        statsSummaryTable[42][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Standard Deviation) [cfs]";
        statsSummaryTable[43][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Variance)";
        statsSummaryTable[44][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Skewness)";
        statsSummaryTable[45][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Coefficient of Variation)";
        
        if(showMonthlyTF){
            statsSummaryTable[46][0] = "January (Maximum) [cfs]";
            statsSummaryTable[47][0] = "January (Minimum) [cfs]";
            statsSummaryTable[48][0] = "January (Upper Quartile) [cfs]";
            statsSummaryTable[49][0] = "January (Lower Quartile) [cfs]";
            statsSummaryTable[50][0] = "January (Median) [cfs]";
            statsSummaryTable[51][0] = "January (Average) [cfs]";
            statsSummaryTable[52][0] = "January (Standard Deviation) [cfs]";
            statsSummaryTable[53][0] = "January (Variance)";
            statsSummaryTable[54][0] = "January (Skewness)";
            statsSummaryTable[55][0] = "January (Coefficient of Variation)";
            statsSummaryTable[56][0] = "February (Maximum) [cfs]";
            statsSummaryTable[57][0] = "February (Minimum) [cfs]";
            statsSummaryTable[58][0] = "February (Upper Quartile) [cfs]";
            statsSummaryTable[59][0] = "February (Lower Quartile) [cfs]";
            statsSummaryTable[60][0] = "February (Median) [cfs]";
            statsSummaryTable[61][0] = "February (Average) [cfs]";
            statsSummaryTable[62][0] = "February (Standard Deviation) [cfs]";
            statsSummaryTable[63][0] = "February (Variance)";
            statsSummaryTable[64][0] = "February (Skewness)";
            statsSummaryTable[65][0] = "February (Coefficient of Variation)";
            statsSummaryTable[66][0] = "March (Maximum) [cfs]";
            statsSummaryTable[67][0] = "March (Minimum) [cfs]";
            statsSummaryTable[68][0] = "March (Upper Quartile) [cfs]";
            statsSummaryTable[69][0] = "March (Lower Quartile) [cfs]";
            statsSummaryTable[70][0] = "March (Median) [cfs]";
            statsSummaryTable[71][0] = "March (Average) [cfs]";
            statsSummaryTable[72][0] = "March (Standard Deviation) [cfs]";
            statsSummaryTable[73][0] = "March (Variance)";
            statsSummaryTable[74][0] = "March (Skewness)";
            statsSummaryTable[75][0] = "March (Coefficient of Variation)";
            statsSummaryTable[76][0] = "April (Maximum) [cfs]";
            statsSummaryTable[77][0] = "April (Minimum) [cfs]";
            statsSummaryTable[78][0] = "April (Upper Quartile) [cfs]";
            statsSummaryTable[79][0] = "April (Lower Quartile) [cfs]";
            statsSummaryTable[80][0] = "April (Median) [cfs]";
            statsSummaryTable[81][0] = "April (Average) [cfs]";
            statsSummaryTable[82][0] = "April (Standard Deviation) [cfs]";
            statsSummaryTable[83][0] = "April (Variance)";
            statsSummaryTable[84][0] = "April (Skewness)";
            statsSummaryTable[85][0] = "April (Coefficient of Variation)";
            statsSummaryTable[86][0] = "May (Maximum) [cfs]";
            statsSummaryTable[87][0] = "May (Minimum) [cfs]";
            statsSummaryTable[88][0] = "May (Upper Quartile) [cfs]";
            statsSummaryTable[89][0] = "May (Lower Quartile) [cfs]";
            statsSummaryTable[90][0] = "May (Median) [cfs]";
            statsSummaryTable[91][0] = "May (Average) [cfs]";
            statsSummaryTable[92][0] = "May (Standard Deviation) [cfs]";
            statsSummaryTable[93][0] = "May (Variance)";
            statsSummaryTable[94][0] = "May (Skewness)";
            statsSummaryTable[95][0] = "May (Coefficient of Variation)";
            statsSummaryTable[96][0] = "June (Maximum) [cfs]";
            statsSummaryTable[97][0] = "June (Minimum) [cfs]";
            statsSummaryTable[98][0] = "June (Upper Quartile) [cfs]";
            statsSummaryTable[99][0] = "June (Lower Quartile) [cfs]";
            statsSummaryTable[100][0] = "June (Median) [cfs]";
            statsSummaryTable[101][0] = "June (Average) [cfs]";
            statsSummaryTable[102][0] = "June (Standard Deviation) [cfs]";
            statsSummaryTable[103][0] = "June (Variance)";
            statsSummaryTable[104][0] = "June (Skewness)";
            statsSummaryTable[105][0] = "June (Coefficient of Variation)";
            statsSummaryTable[106][0] = "July (Maximum) [cfs]";
            statsSummaryTable[107][0] = "July (Minimum) [cfs]";
            statsSummaryTable[108][0] = "July (Upper Quartile) [cfs]";
            statsSummaryTable[109][0] = "July (Lower Quartile) [cfs]";
            statsSummaryTable[110][0] = "July (Median) [cfs]";
            statsSummaryTable[111][0] = "July (Average) [cfs]";
            statsSummaryTable[112][0] = "July (Standard Deviation) [cfs]";
            statsSummaryTable[113][0] = "July (Variance)";
            statsSummaryTable[114][0] = "July (Skewness)";
            statsSummaryTable[115][0] = "July (Coefficient of Variation)";
            statsSummaryTable[116][0] = "August (Maximum) [cfs]";
            statsSummaryTable[117][0] = "August (Minimum) [cfs]";
            statsSummaryTable[118][0] = "August (Upper Quartile) [cfs]";
            statsSummaryTable[119][0] = "August (Lower Quartile) [cfs]";
            statsSummaryTable[120][0] = "August (Median) [cfs]";
            statsSummaryTable[121][0] = "August (Average) [cfs]";
            statsSummaryTable[122][0] = "August (Standard Deviation) [cfs]";
            statsSummaryTable[123][0] = "August (Variance)";
            statsSummaryTable[124][0] = "August (Skewness)";
            statsSummaryTable[125][0] = "August (Coefficient of Variation)";
            statsSummaryTable[126][0] = "September (Maximum) [cfs]";
            statsSummaryTable[127][0] = "September (Minimum) [cfs]";
            statsSummaryTable[128][0] = "September (Upper Quartile) [cfs]";
            statsSummaryTable[129][0] = "September (Lower Quartile) [cfs]";
            statsSummaryTable[130][0] = "September (Median) [cfs]";
            statsSummaryTable[131][0] = "September (Average) [cfs]";
            statsSummaryTable[132][0] = "September (Standard Deviation) [cfs]";
            statsSummaryTable[133][0] = "September (Variance)";
            statsSummaryTable[134][0] = "September (Skewness)";
            statsSummaryTable[135][0] = "September (Coefficient of Variation)";
            statsSummaryTable[136][0] = "October (Maximum) [cfs]";
            statsSummaryTable[137][0] = "October (Minimum) [cfs]";
            statsSummaryTable[138][0] = "October (Upper Quartile) [cfs]";
            statsSummaryTable[139][0] = "October (Lower Quartile) [cfs]";
            statsSummaryTable[140][0] = "October (Median) [cfs]";
            statsSummaryTable[141][0] = "October (Average) [cfs]";
            statsSummaryTable[142][0] = "October (Standard Deviation) [cfs]";
            statsSummaryTable[143][0] = "October (Variance)";
            statsSummaryTable[144][0] = "October (Skewness)";
            statsSummaryTable[145][0] = "October (Coefficient of Variation)";
            statsSummaryTable[146][0] = "November (Maximum) [cfs]";
            statsSummaryTable[147][0] = "November (Minimum) [cfs]";
            statsSummaryTable[148][0] = "November (Upper Quartile) [cfs]";
            statsSummaryTable[149][0] = "November (Lower Quartile) [cfs]";
            statsSummaryTable[150][0] = "November (Median) [cfs]";
            statsSummaryTable[151][0] = "November (Average) [cfs]";
            statsSummaryTable[152][0] = "November (Standard Deviation) [cfs]";
            statsSummaryTable[153][0] = "November (Variance)";
            statsSummaryTable[154][0] = "November (Skewness)";
            statsSummaryTable[155][0] = "November (Coefficient of Variation)";
            statsSummaryTable[156][0] = "December (Maximum) [cfs]";
            statsSummaryTable[157][0] = "December (Minimum) [cfs]";
            statsSummaryTable[158][0] = "December (Upper Quartile) [cfs]";
            statsSummaryTable[159][0] = "December (Lower Quartile) [cfs]";
            statsSummaryTable[160][0] = "December (Median) [cfs]";
            statsSummaryTable[161][0] = "December (Average) [cfs]";
            statsSummaryTable[162][0] = "December (Standard Deviation) [cfs]";
            statsSummaryTable[163][0] = "December (Variance)";
            statsSummaryTable[164][0] = "December (Skewness)";
            statsSummaryTable[165][0] = "December (Coefficient of Variation)";
            statsSummaryTable[166][0] = "Flow Statistics based on Indicators of Hydrologic Alteration from:";
            statsSummaryTable[167][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.\t";
            statsSummaryTable[168][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.\t";
            statsSummaryTable[169][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.";
        }else{
            statsSummaryTable[46][0] = "Flow Statistics based on Indicators of Hydrologic Alteration from:";
            statsSummaryTable[47][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.\t";
            statsSummaryTable[48][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.\t";
            statsSummaryTable[49][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.";
        }
        
        //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());
        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){
            int ctr = 0;
            for(int i=0; i<mQnFlows.size(); i++){
                if(mQnFlows.get(i) != Double.NaN){
                    ctr++;
                }
            }
            String[][] mQnData = new String[ctr][2];
            for(int i=0; i<mQnFlows.size(); i++){
                if(mQnFlows.get(i) != Double.NaN){
                    mQnData[ctr][0] = String.valueOf(ctr);
                    mQnData[ctr][1] = String.valueOf(mQnFlows.get(i));
                    ctr++;
                }
            }
            double[][] mQnRanks = doubleArray.weibullPlottingPosition(mQnData);

            //Find the "n" recurrence interval and return its corresponding flow as the mQnFlow
            double target = (double) n/10;
            for(int i=0; i<(mQnRanks.length - 1); i++){
                if(n < mQnRanks[i][0] && n > mQnRanks[i+1][0]){
                    //Linear interpolation for flow value for "n" recurrence interval
                    mQnFlow = ((target - mQnRanks[i+1][0])/(mQnRanks[i][0] - mQnRanks[i+1][0]))*(mQnRanks[i][1] - mQnRanks[i+1][1]) + mQnRanks[i+1][1];
                }else if(n == mQnRanks[i][0]){
                    mQnFlow = mQnRanks[i][1];
                }
            }
        }
        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;
    }
    /**
     * 
     * @param statsSummaryTable
     * @param flowData
     * @param highPercentile
     * @param lowPercentile
     * @param m
     * @param dataHeader
     * @param showMonthlyTF
     * @param seasonBegin
     * @param seasonEnd
     * @return
     * @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> average_1Day = new ArrayList<Double>();
        ArrayList<Double> diffPositive = new ArrayList<Double>();
        ArrayList<Double> diffNegative = new ArrayList<Double>();
        double max_1day = -9999;
        double min_1day = 9999;
        double oldValue = 0;
        String max_1day_date = "-1";
        String 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;
        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]);
            average_1Day.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);
                }

                //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.Average(average_1Day);
        double centroid = centroidSum / sum;
        
        //Calculate 3-day statistics
        Object[] resultArray = getMdayData(flowData, 3);
        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 = getMdayData(flowData, 7);
        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 = getMdayData(flowData, 30);
        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 = getMdayData(flowData, 90);
        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 = getMdayData(flowData, m);
            ArrayList<Double> average_Mday = (ArrayList<Double>) resultArray[1];
            min_Mday = doubleMath.min(average_Mday);
        }
        
        //Calculate Pulse Information
        double highLimit = doubleMath.Percentile_function(average_1Day, highPercentile);
        double lowLimit = doubleMath.Percentile_function(average_1Day, 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 = 50;
        if(showMonthlyTF) summarySize = 170;
        String[] additionalSummary = new String[summarySize];
        additionalSummary[0] = "";//blank
        additionalSummary[1] = dataHeader;//Method
        additionalSummary[2] = String.valueOf(doubleMath.round(max_1day,3));//Maximum (1-day)
        additionalSummary[3] = max_1day_date;//Date of Maximum (1-day)
        additionalSummary[4] = String.valueOf(doubleMath.round(min_1day,3));//Minimum (1-day)
        additionalSummary[5] = min_1day_date;//Date of Minimum (1-day)
        additionalSummary[6] = String.valueOf(doubleMath.round(max_3day,3));//Maximum (3-day)
        additionalSummary[7] = max_3day_date;//Dates of Maximum (3-day)
        additionalSummary[8] = String.valueOf(doubleMath.round(min_3day,3));//Minimum (3-day)
        additionalSummary[9] = min_3day_date;//Dates of Minimum (3-day)
        additionalSummary[10] = String.valueOf(doubleMath.round(max_7day,3));//Maximum (7-day)
        additionalSummary[11] = max_7day_date;//Dates of Maximum (7-day)
        additionalSummary[12] = String.valueOf(doubleMath.round(min_7day,3));//Minimum (7-day)
        additionalSummary[13] = min_7day_date;//Dates of Minimum (7-day)
        additionalSummary[14] = String.valueOf(doubleMath.round(min_7day_ave,3));//Minimum (7-day)
        additionalSummary[15] = String.valueOf(doubleMath.round(max_30day,3));//Maximum (30-day)
        additionalSummary[16] = max_30day_date;//Dates of Maximum (30-day)
        additionalSummary[17] = String.valueOf(doubleMath.round(min_30day,3));//Minimum (30-day)
        additionalSummary[18] = min_30day_date;//Dates of Minimum (30-day)
        additionalSummary[19] = String.valueOf(doubleMath.round(max_90day,3));//Maximum (90-day)
        additionalSummary[20] = max_90day_date;//Dates of Maximum (90-day)
        additionalSummary[21] = String.valueOf(doubleMath.round(min_90day,3));//Minimum (90-day)
        additionalSummary[22] = min_90day_date;//Dates of Minimum (90-day)
        additionalSummary[23] = String.valueOf(ctr_zero);//Number of Zero Flow Days
        additionalSummary[24] = String.valueOf(ctr_reversals);//Number of Flow Reversals
        additionalSummary[25] = String.valueOf(ctr_rises);//Number of Flow Rises
        additionalSummary[26] = String.valueOf(ctr_falls);//Number of Flow Falls
        additionalSummary[27] = String.valueOf(ctr_highPulse);//Number of High Pulses
        additionalSummary[28] = String.valueOf(doubleMath.round(highLimit,1));//Threshold for High Pulses
        additionalSummary[29] = String.valueOf(doubleMath.round(doubleMath.Average(highPulses), 3));//Average Duration of High Pulses
        additionalSummary[30] = String.valueOf(ctr_lowPulse);//Number of Low Pulses
        additionalSummary[31] = String.valueOf(doubleMath.round(lowLimit,1));//Threshold for Low Pulses
        additionalSummary[32] = String.valueOf(doubleMath.round(doubleMath.Average(lowPulses), 3));//Average Duration of Low Pulses
        additionalSummary[33] = String.valueOf(doubleMath.round(doubleMath.Average(diffPositive),3));//Average Positive Difference Between Consecutive Days
        additionalSummary[34] = String.valueOf(doubleMath.round(doubleMath.Average(diffNegative),3));//Average Negative Difference Between Consecutive Days
        additionalSummary[35] = String.valueOf(doubleMath.round(centroid,2));//Temporal centroid of annual discharge (Julian day, not water-year day)
        
        //Add seasonal stats summary
        int index = 36;
        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
        }else{
            //References section blanks
            additionalSummary[index] = "";//blank
            additionalSummary[index + 1] = "";//blank
            additionalSummary[index + 2] = "";//blank
            additionalSummary[index + 3] = "";//blank
        }
        
        //Add these statistics to the existing results
        statsSummaryTable = doubleArray.appendcolumn_Matrix(statsSummaryTable, additionalSummary);
        
        Object[] returnArray = {statsSummaryTable, min_Mday};
        return returnArray;
    }
    /**
     * 
     * @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(doubleMath.round(doubleMath.max(data), 3));//Maximum
        statsSummaryTable[index + 1] = String.valueOf(doubleMath.round(doubleMath.min(data), 3));//Minimum
        statsSummaryTable[index + 2] = String.valueOf(doubleMath.round(doubleMath.Percentile_function(data, 0.75), 3));//Upper Quartile
        statsSummaryTable[index + 3] = String.valueOf(doubleMath.round(doubleMath.Percentile_function(data,0.25), 3));//Lower Quartile
        statsSummaryTable[index + 4] = String.valueOf(doubleMath.round(doubleMath.Median(data), 3));//Median
        statsSummaryTable[index + 5] = String.valueOf(doubleMath.round(doubleMath.Average(data), 3));//Average
        statsSummaryTable[index + 6] = String.valueOf(doubleMath.round(doubleMath.StandardDeviationSample(data), 3));//Standard Deviation
        statsSummaryTable[index + 7] = String.valueOf(doubleMath.round(doubleMath.VarianceSample(data), 3));//Variance
        statsSummaryTable[index + 8] = String.valueOf(doubleMath.round(doubleMath.SkewnessSample(data), 3));//Skewness
        statsSummaryTable[index + 9] = String.valueOf(doubleMath.round(doubleMath.CoefficientOfVariation(data), 3));//January (Coefficient of Variation
        
        index = index + 10;
        Object[] returnArray = {statsSummaryTable, index};
        return returnArray;
    }
    /**
     * Loops through and finds "m-day" consecutive values and takes the average of them
     * @param flowData  a string[][] containing: column1 = dates, column2 = flowValues
     * @param numDays  an integer representing the number (m) of consecutive days to be desired for analysis
     * @returns  an ArrayList containing an ArrayList of each set of "m-day" consecutive set of flows for analysis (min, max, average, etc)
     * @throws IOException
     */
    private Object[] getMdayData(String[][] flowData, int numDays) throws IOException{
        DoubleMath doubleMath = new DoubleMath();
        DoubleArray doubleArray = new DoubleArray();
        
        //Loop through flow data and find "m"-day consecutive flows
        ArrayList<String> allDate = new ArrayList<String>();
        ArrayList<Double> allData = new ArrayList<Double>();
        try{
            for(int i=0; i<flowData.length; i++){
                ArrayList<String> mDayDate = new ArrayList<String>();
                ArrayList<Double> mDayData = new ArrayList<Double>();
                int ctr = i;
                for(int j=0; j<numDays; j++){
                    if(j==0){
                        //Keep the first day
                        mDayDate.add(flowData[ctr][0]);
                        mDayData.add(Double.parseDouble(flowData[ctr][1]));
                    }else{
                        //Compare the current day to the previous day for consecutive-ness
                        boolean checkNextDate = doubleArray.checkSubsequentDates(flowData[ctr-1][0], flowData[ctr][0]);
                        if(checkNextDate){
                            mDayDate.add(flowData[ctr][0]);
                            mDayData.add(Double.parseDouble(flowData[ctr][1]));
                        }else{
                            //If not consecutive days, break out of the loop and move to the next date for flowData
                            mDayDate.clear();
                            mDayData.clear();
                            i = ctr - 1;//Skip to newest date since there is a break in the consecutive day data
                            break;
                        }
                    }
                    ctr++;
                }
                if(mDayData.size() == numDays){
                    //Add this m-consecutive day set of data to the all data array list for statistics later
                    String startDate = mDayDate.get(0);
                    String endDate = mDayDate.get(numDays - 1);
                    allDate.add(startDate + " to " + endDate);
                    allData.add(doubleMath.Average(mDayData));
                }
            }
        }catch(ArrayIndexOutOfBoundsException e){
            //If the consecutive day counter (ctr) goes beyond the length of data available,
            //stop the subroutine and return the existing results
            Object[] returnArray = {allDate, allData};
            return returnArray;
        }
        
        Object[] returnArray = {allDate, allData};
        return returnArray;
    }
    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;
    }
    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);
    }
}