private ForecastTable getForecastedTable(decimal[] array, int forecasting_number, int forecasting_Type) { Console.WriteLine(forecasting_Type + "----" + forecasting_number); if (forecasting_Type == 0) { ForecastTable ft = fm.naive(array, forecasting_number, 0); return(ft); } else if (forecasting_Type == 1) { ForecastTable ft = fm.simpleMovingAverage(array, forecasting_number, 3, 0); return(ft); } else if (forecasting_Type == 2) { ForecastTable ft = fm.weightedMovingAverage(array, forecasting_number, (Decimal)0.05, (Decimal)0.15, (Decimal)0.8); return(ft); } else if (forecasting_Type == 3) { ForecastTable ft = fm.adaptiveRateSmoothing(array, forecasting_number, (Decimal)0.2, (Decimal)0.8); return(ft); } else { ForecastTable ft = fm.exponentialSmoothing(array, forecasting_number, (Decimal)0.8); return(ft); } }
private void button_ForecastedDataView_Click_1(object sender, EventArgs e) { int month_min = 0; int month_max = 0; if (isMonth) { month_min = comboBox_ForecastedDateRange.SelectedIndex + 1; month_max = 25; } else { if (comboBox_ForecastedDateRange.SelectedIndex == 0) { month_min = 1; month_max = 3; } else if (comboBox_ForecastedDateRange.SelectedIndex == 1) { month_min = 4; month_max = 6; } else if (comboBox_ForecastedDateRange.SelectedIndex == 2) { month_min = 7; month_max = 9; } else { month_min = 10; month_max = 12; } } if (comboBox_ForecastedRegion.SelectedItem != null) { int region_id = (comboBox_ForecastedRegion.SelectedItem as Region).region_id; int forecastingType = comboBox_ForecastingType.SelectedIndex; string number = textBox_ForecastingNumber.Text; ForecastTable ft = dataArranger.getForecastTable(region_id, forecasted_yData_type, month_min, month_max, forecastingType, number, this); this.grid_ForecastedData.DataSource = ft; if (ft != null) { this.panel_forecastedGrid.Show(); ForecastingMethods fm = new ForecastingMethods(); this.textBox_MSE.Text = fm.MeanSignedError(ft, false, DEFAULT_IGNORE).ToString(); this.textBox_MAE.Text = fm.MeanAbsoluteError(ft, false, DEFAULT_IGNORE).ToString(); this.textBox_MPE.Text = fm.MeanPercentError(ft, false, DEFAULT_IGNORE).ToString(); this.textBox_MAPE.Text = fm.MeanAbsolutePercentError(ft, false, DEFAULT_IGNORE).ToString(); this.textBox_TS.Text = fm.TrackingSignal(ft, false, DEFAULT_IGNORE).ToString(); this.textBox_MSqE.Text = fm.MeanSquaredError(ft, false, DEFAULT_IGNORE, 0).ToString(); this.textBox_CSE.Text = fm.CumulativeSignedError(ft, false, DEFAULT_IGNORE).ToString(); this.textBox_CAE.Text = fm.CumulativeAbsoluteError(ft, false, DEFAULT_IGNORE).ToString(); } } }
// // Adaptive Rate Smoothing // public ForecastTable adaptiveRateSmoothing(decimal[] values, int Extension, decimal MinGamma, decimal MaxGamma) { ForecastTable dt = new ForecastTable(); for (Int32 i = 0; i < (values.Length + Extension); i++) { //Insert a row for each value in set DataRow row = dt.NewRow(); dt.Rows.Add(row); row.BeginEdit(); //assign its sequence number row["Instance"] = i; if (i < values.Length) { row["Value"] = values[i]; if (i == 0) {//initialize first row row["Forecast"] = values[i]; } else {//calculate gamma and forecast value DataRow priorRow = dt.Select("Instance=" + (i - 1).ToString())[0]; decimal PriorForecast = (Decimal)priorRow["Forecast"]; decimal PriorValue = (Decimal)priorRow["Value"]; decimal Gamma = Math.Abs(TrackingSignal(dt, false, 3)); if (Gamma < MinGamma) Gamma = MinGamma; if (Gamma > MaxGamma) Gamma = MaxGamma; row["Forecast"] = PriorForecast + (Gamma * (PriorValue - PriorForecast)); } } else {//extension set, can't use actual values anymore DataRow priorRow = dt.Select("Instance=" + (i - 1).ToString())[0]; decimal PriorForecast = (Decimal)priorRow["Forecast"]; decimal PriorValue = (Decimal)priorRow["Forecast"]; decimal Gamma = Math.Abs(TrackingSignal(dt, false, 3)); if (Gamma < MinGamma) Gamma = MinGamma; if (Gamma > MaxGamma) Gamma = MaxGamma; row["Forecast"] = PriorForecast + (Gamma * (PriorValue - PriorForecast)); } row.EndEdit(); } dt.AcceptChanges(); return dt; }
//CumulativeAbsoluteError = Sum( |E(t)| ) public decimal CumulativeAbsoluteError(ForecastTable dt, bool Holdout, int IgnoreInitial) { string Filter = "AbsoluteError Is Not Null AND Instance > " + IgnoreInitial.ToString(); if (Holdout) Filter += " AND Holdout=True"; if (dt.Select(Filter).Length == 0) return 0; return (Decimal)dt.Compute("SUM(AbsoluteError)", Filter); }
//Tracking signal = MeanSignedError / MeanAbsoluteError public decimal TrackingSignal(ForecastTable dt, bool Holdout, int IgnoreInitial) { decimal MAE = MeanAbsoluteError(dt, Holdout, IgnoreInitial); if (MAE == 0) { return(0); } return(MeanSignedError(dt, Holdout, IgnoreInitial) / MAE); }
internal ForecastTable getForecastTable(int region_id, int forecasted_yData_type, int month_min, int month_max, int forecasting_Type, string number, Main_Form main_Form) { int tempnumber; bool result = Int32.TryParse(number, out tempnumber); if (!result) { MetroFramework.MetroMessageBox.Show(main_Form, "Forecasting number should be an integer value" + "\n" + "Please insert a correct value", "Forecasting number is not an integer", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { if (month_max > 12) { DataSet ds = dr.getGraphDataOnMonth(region_id, month_min); int count = ds.Tables[0].Rows.Count; Console.WriteLine(count); decimal[] array = new decimal[count]; if (forecasted_yData_type == 1) { int index = 0; foreach (DataRow row in ds.Tables[0].Rows) { array[index] = Convert.ToDecimal(row["quantity"].ToString()); index++; } int forecasting_number = Convert.ToInt32(number); ForecastTable ft = this.getForecastedTable(array, forecasting_number, forecasting_Type);//fm.naive(array, 10, 0); return(ft); } else { foreach (DataRow row in ds.Tables[0].Rows) { decimal income = Convert.ToDecimal(row["total_income"].ToString()); } ForecastTable ft = fm.naive(array, 10, 0); return(ft); } } else { return(null); } } return(null); }
//CumulativeAbsoluteError = Sum( |E(t)| ) public decimal CumulativeAbsoluteError(ForecastTable dt, bool Holdout, int IgnoreInitial) { string Filter = "AbsoluteError Is Not Null AND Instance > " + IgnoreInitial.ToString(); if (Holdout) { Filter += " AND Holdout=True"; } if (dt.Select(Filter).Length == 0) { return(0); } return((Decimal)dt.Compute("SUM(AbsoluteError)", Filter)); }
//Bayes: use prior actual value as forecast public ForecastTable naive(decimal[] values, int Extension, int Holdout) { ForecastTable dt = new ForecastTable(); try { for (Int32 i = 0; i < (values.Length + Extension); i++) { //Insert a row for each value in set DataRow row = dt.NewRow(); dt.Rows.Add(row); row.BeginEdit(); //assign its sequence number row["Instance"] = i; //if i is in the holdout range of values //row["Holdout"] = (i > (values.Length - 1 - Holdout)) && (i < values.Length); if (i < values.Length) { //processing values which actually occurred //Assign the actual value to the DataRow row["Value"] = values[i]; if (i == 0) { //first row, value gets itself row["Forecast"] = values[i]; } else { //Put the prior row's value into the current row's forecasted value row["Forecast"] = values[i - 1]; } } else {//Extension rows row["Forecast"] = values[values.Length - 1]; } row.EndEdit(); } dt.AcceptChanges(); return(dt); } catch (Exception ex) { } return(null); }
// //Exponential Smoothing // // F(t+1) = ( Alpha * D(t) ) + (1 - Alpha) * F(t) // public ForecastTable exponentialSmoothing(decimal[] values, int Extension, decimal Alpha) { ForecastTable dt = new ForecastTable(); for (Int32 i = 0; i < (values.Length + Extension); i++) { //Insert a row for each value in set DataRow row = dt.NewRow(); dt.Rows.Add(row); row.BeginEdit(); //assign its sequence number row["Instance"] = i; if (i < values.Length) {//test set row["Value"] = values[i]; if (i == 0) {//initialize first value row["Forecast"] = values[i]; } else {//main area of forecasting DataRow priorRow = dt.Select("Instance=" + (i - 1).ToString())[0]; decimal PriorForecast = (Decimal)priorRow["Forecast"]; decimal PriorValue = (Decimal)priorRow["Value"]; row["Forecast"] = PriorForecast + (Alpha * (PriorValue - PriorForecast)); } } else {//extension has to use prior forecast instead of prior value DataRow priorRow = dt.Select("Instance=" + (i - 1).ToString())[0]; decimal PriorForecast = (Decimal)priorRow["Forecast"]; decimal PriorValue = (Decimal)priorRow["Forecast"]; row["Forecast"] = PriorForecast + (Alpha * (PriorValue - PriorForecast)); } row.EndEdit(); } dt.AcceptChanges(); return(dt); }
//MSE = Sum( E(t)^2 ) / n public decimal MeanSquaredError(ForecastTable dt, bool Holdout, int IgnoreInitial, int DegreesOfFreedom) { decimal SquareError = 0; string Filter = "Error Is Not Null AND Instance > " + IgnoreInitial.ToString(); if (Holdout) { Filter += " AND Holdout=True"; } DataRow[] rows = dt.Select(Filter); if (rows.Length == 0) { return(0); } foreach (DataRow row in rows) { SquareError = (Decimal)Math.Pow(Double.Parse(row["Error"].ToString()), (Double)2.0); } return(SquareError / (dt.Rows.Count - DegreesOfFreedom)); }
//Tracking signal = MeanSignedError / MeanAbsoluteError public decimal TrackingSignal(ForecastTable dt, bool Holdout, int IgnoreInitial) { decimal MAE = MeanAbsoluteError(dt, Holdout, IgnoreInitial); if (MAE == 0) return 0; return MeanSignedError(dt, Holdout, IgnoreInitial) / MAE; }
// // Adaptive Rate Smoothing // public ForecastTable adaptiveRateSmoothing(decimal[] values, int Extension, decimal MinGamma, decimal MaxGamma) { ForecastTable dt = new ForecastTable(); for (Int32 i = 0; i < (values.Length + Extension); i++) { //Insert a row for each value in set DataRow row = dt.NewRow(); dt.Rows.Add(row); row.BeginEdit(); //assign its sequence number row["Instance"] = i; if (i < values.Length) { row["Value"] = values[i]; if (i == 0) {//initialize first row row["Forecast"] = values[i]; } else {//calculate gamma and forecast value DataRow priorRow = dt.Select("Instance=" + (i - 1).ToString())[0]; decimal PriorForecast = (Decimal)priorRow["Forecast"]; decimal PriorValue = (Decimal)priorRow["Value"]; decimal Gamma = Math.Abs(TrackingSignal(dt, false, 3)); if (Gamma < MinGamma) { Gamma = MinGamma; } if (Gamma > MaxGamma) { Gamma = MaxGamma; } row["Forecast"] = PriorForecast + (Gamma * (PriorValue - PriorForecast)); } } else {//extension set, can't use actual values anymore DataRow priorRow = dt.Select("Instance=" + (i - 1).ToString())[0]; decimal PriorForecast = (Decimal)priorRow["Forecast"]; decimal PriorValue = (Decimal)priorRow["Forecast"]; decimal Gamma = Math.Abs(TrackingSignal(dt, false, 3)); if (Gamma < MinGamma) { Gamma = MinGamma; } if (Gamma > MaxGamma) { Gamma = MaxGamma; } row["Forecast"] = PriorForecast + (Gamma * (PriorValue - PriorForecast)); } row.EndEdit(); } dt.AcceptChanges(); return(dt); }
// //Exponential Smoothing // // F(t+1) = ( Alpha * D(t) ) + (1 - Alpha) * F(t) // public ForecastTable exponentialSmoothing(decimal[] values, int Extension, decimal Alpha) { ForecastTable dt = new ForecastTable(); for (Int32 i = 0; i < (values.Length + Extension); i++) { //Insert a row for each value in set DataRow row = dt.NewRow(); dt.Rows.Add(row); row.BeginEdit(); //assign its sequence number row["Instance"] = i; if (i < values.Length) {//test set row["Value"] = values[i]; if (i == 0) {//initialize first value row["Forecast"] = values[i]; } else {//main area of forecasting DataRow priorRow = dt.Select("Instance=" + (i - 1).ToString())[0]; decimal PriorForecast = (Decimal)priorRow["Forecast"]; decimal PriorValue = (Decimal)priorRow["Value"]; row["Forecast"] = PriorForecast + (Alpha * (PriorValue - PriorForecast)); } } else {//extension has to use prior forecast instead of prior value DataRow priorRow = dt.Select("Instance=" + (i - 1).ToString())[0]; decimal PriorForecast = (Decimal)priorRow["Forecast"]; decimal PriorValue = (Decimal)priorRow["Forecast"]; row["Forecast"] = PriorForecast + (Alpha * (PriorValue - PriorForecast)); } row.EndEdit(); } dt.AcceptChanges(); return dt; }
//MeanPercentError = Sum( PercentError ) / n public decimal MeanPercentError(ForecastTable dt, bool Holdout, int IgnoreInitial) { string Filter = "PercentError Is Not Null AND Instance > " + IgnoreInitial.ToString(); if (Holdout) Filter += " AND Holdout=True"; if (dt.Select(Filter).Length == 0) return 0; return (Decimal)dt.Compute("Avg(PercentError)", Filter); }
//MSE = Sum( E(t)^2 ) / n public decimal MeanSquaredError(ForecastTable dt, bool Holdout, int IgnoreInitial, int DegreesOfFreedom) { decimal SquareError = 0; string Filter = "Error Is Not Null AND Instance > " + IgnoreInitial.ToString(); if (Holdout) Filter += " AND Holdout=True"; DataRow[] rows = dt.Select(Filter); if (rows.Length == 0) return 0; foreach (DataRow row in rows) { SquareError = (Decimal)Math.Pow(Double.Parse(row["Error"].ToString()), (Double)2.0); } return SquareError / (dt.Rows.Count - DegreesOfFreedom); }
// //Weighted Moving Average // // F(t+1) = (Weight1 * D(t)) + (Weight2 * D(t-1)) + (Weight3 * D(t-2)) + ... + (WeightN * D(t-n+1)) // public ForecastTable weightedMovingAverage(decimal[] values, int Extension, params decimal[] PeriodWeight) { //PeriodWeight[].Length is used to determine the number of periods over which to average //PeriodWeight[x] is used to apply a weight to the prior period's value //Make sure PeriodWeight values add up to 100% decimal test = 0; foreach (decimal weight in PeriodWeight) { test += weight; } if (test != 1) { MessageBox.Show("Period weights must add up to 1.0", "Invalid parameters", MessageBoxButtons.OK); return(null); } ForecastTable dt = new ForecastTable(); for (Int32 i = 0; i < values.Length + Extension; i++) { //Insert a row for each value in set DataRow row = dt.NewRow(); dt.Rows.Add(row); row.BeginEdit(); //assign its sequence number row["Instance"] = i; if (i < values.Length) {//we're in the test set row["Value"] = values[i]; } if (i == 0) {//initialize forecast with first row's value row["Forecast"] = values[i]; } else if ((i < values.Length) && (i < PeriodWeight.Length)) {//processing one of the first rows, before we've advanced enough to properly weight past rows decimal avg = 0; //Get the datarows representing the values within the WMA length DataRow[] rows = dt.Select("Instance>=" + (i - PeriodWeight.Length).ToString() + " AND Instance < " + i.ToString(), "Instance"); for (int j = 0; j < rows.Length; j++) {//apply an initial, uniform weight (1 / rows.Length) to the initial rows avg += (Decimal)rows[j]["Value"] * (1 / rows.Length); } row["Forecast"] = avg; } else if ((i < values.Length) && (i >= PeriodWeight.Length)) {//Out of initial rows and processing the test set decimal avg = 0; //Get the rows within the weight range just prior to the current row DataRow[] rows = dt.Select("Instance>=" + (i - PeriodWeight.Length).ToString() + " AND Instance < " + i.ToString(), "Instance"); for (int j = 0; j <= rows.Length - 1; j++) {//Apply the appropriate period's weight to the value avg += (Decimal)rows[j]["Value"] * PeriodWeight[j]; } //Assign the forecasted value to the current row row["Forecast"] = avg; } else {//into the extension decimal avg = 0; DataRow[] rows = dt.Select("Instance>=" + (i - PeriodWeight.Length).ToString() + " AND Instance < " + i.ToString(), "Instance"); for (int j = 0; j < rows.Length; j++) {//with no actual values to weight, use the previous rows' forecast instead avg += (Decimal)rows[j]["Forecast"] * PeriodWeight[j]; } row["Forecast"] = avg; } row.EndEdit(); } dt.AcceptChanges(); return(dt); }
//Bayes: use prior actual value as forecast public ForecastTable naive(decimal[] values, int Extension, int Holdout) { ForecastTable dt = new ForecastTable(); try { for (Int32 i = 0; i < (values.Length + Extension); i++) { //Insert a row for each value in set DataRow row = dt.NewRow(); dt.Rows.Add(row); row.BeginEdit(); //assign its sequence number row["Instance"] = i; //if i is in the holdout range of values //row["Holdout"] = (i > (values.Length - 1 - Holdout)) && (i < values.Length); if (i < values.Length) { //processing values which actually occurred //Assign the actual value to the DataRow row["Value"] = values[i]; if (i == 0) { //first row, value gets itself row["Forecast"] = values[i]; } else { //Put the prior row's value into the current row's forecasted value row["Forecast"] = values[i - 1]; } } else {//Extension rows row["Forecast"] = values[values.Length - 1]; } row.EndEdit(); } dt.AcceptChanges(); return dt; } catch (Exception ex) { } return null; }
// //Simple Moving Average // // ( Dt + D(t-1) + D(t-2) + ... + D(t-n+1) ) // F(t+1) = ----------------------------------------- // n public ForecastTable simpleMovingAverage(decimal[] values, int Extension, int Periods, int Holdout) { ForecastTable dt = new ForecastTable(); for (Int32 i = 0; i < values.Length + Extension; i++) { //Insert a row for each value in set DataRow row = dt.NewRow(); dt.Rows.Add(row); row.BeginEdit(); //assign its sequence number row["Instance"] = i; if (i < values.Length) {//processing values which actually occurred row["Value"] = values[i]; } //Indicate if this is a holdout row //row["Holdout"] = (i > (values.Length - Holdout)) && (i < values.Length); if (i == 0) {//Initialize first row with its own value row["Forecast"] = values[i]; } else if (i <= values.Length - Holdout) {//processing values which actually occurred, but not in holdout set decimal avg = 0; DataRow[] rows = dt.Select("Instance>=" + (i - Periods).ToString() + " AND Instance < " + i.ToString(), "Instance"); foreach (DataRow priorRow in rows) { avg += (Decimal)priorRow["Value"]; } avg /= rows.Length; row["Forecast"] = avg; } else {//must be in the holdout set or the extension decimal avg = 0; //get the Periods-prior rows and calculate an average actual value DataRow[] rows = dt.Select("Instance>=" + (i - Periods).ToString() + " AND Instance < " + i.ToString(), "Instance"); foreach (DataRow priorRow in rows) { if ((Int32)priorRow["Instance"] < values.Length) {//in the test or holdout set avg += (Decimal)priorRow["Value"]; } else {//extension, use forecast since we don't have an actual value avg += (Decimal)priorRow["Forecast"]; } } avg /= rows.Length; //set the forecasted value row["Forecast"] = avg; } row.EndEdit(); } dt.AcceptChanges(); return dt; }
// //Simple Moving Average // // ( Dt + D(t-1) + D(t-2) + ... + D(t-n+1) ) // F(t+1) = ----------------------------------------- // n public ForecastTable simpleMovingAverage(decimal[] values, int Extension, int Periods, int Holdout) { ForecastTable dt = new ForecastTable(); for (Int32 i = 0; i < values.Length + Extension; i++) { //Insert a row for each value in set DataRow row = dt.NewRow(); dt.Rows.Add(row); row.BeginEdit(); //assign its sequence number row["Instance"] = i; if (i < values.Length) {//processing values which actually occurred row["Value"] = values[i]; } //Indicate if this is a holdout row //row["Holdout"] = (i > (values.Length - Holdout)) && (i < values.Length); if (i == 0) {//Initialize first row with its own value row["Forecast"] = values[i]; } else if (i <= values.Length - Holdout) {//processing values which actually occurred, but not in holdout set decimal avg = 0; DataRow[] rows = dt.Select("Instance>=" + (i - Periods).ToString() + " AND Instance < " + i.ToString(), "Instance"); foreach (DataRow priorRow in rows) { avg += (Decimal)priorRow["Value"]; } avg /= rows.Length; row["Forecast"] = avg; } else {//must be in the holdout set or the extension decimal avg = 0; //get the Periods-prior rows and calculate an average actual value DataRow[] rows = dt.Select("Instance>=" + (i - Periods).ToString() + " AND Instance < " + i.ToString(), "Instance"); foreach (DataRow priorRow in rows) { if ((Int32)priorRow["Instance"] < values.Length) {//in the test or holdout set avg += (Decimal)priorRow["Value"]; } else {//extension, use forecast since we don't have an actual value avg += (Decimal)priorRow["Forecast"]; } } avg /= rows.Length; //set the forecasted value row["Forecast"] = avg; } row.EndEdit(); } dt.AcceptChanges(); return(dt); }
// //Weighted Moving Average // // F(t+1) = (Weight1 * D(t)) + (Weight2 * D(t-1)) + (Weight3 * D(t-2)) + ... + (WeightN * D(t-n+1)) // public ForecastTable weightedMovingAverage(decimal[] values, int Extension, params decimal[] PeriodWeight) { //PeriodWeight[].Length is used to determine the number of periods over which to average //PeriodWeight[x] is used to apply a weight to the prior period's value //Make sure PeriodWeight values add up to 100% decimal test = 0; foreach (decimal weight in PeriodWeight) { test += weight; } if (test != 1) { MessageBox.Show("Period weights must add up to 1.0", "Invalid parameters", MessageBoxButtons.OK); return null; } ForecastTable dt = new ForecastTable(); for (Int32 i = 0; i < values.Length + Extension; i++) { //Insert a row for each value in set DataRow row = dt.NewRow(); dt.Rows.Add(row); row.BeginEdit(); //assign its sequence number row["Instance"] = i; if (i < values.Length) {//we're in the test set row["Value"] = values[i]; } if (i == 0) {//initialize forecast with first row's value row["Forecast"] = values[i]; } else if ((i < values.Length) && (i < PeriodWeight.Length)) {//processing one of the first rows, before we've advanced enough to properly weight past rows decimal avg = 0; //Get the datarows representing the values within the WMA length DataRow[] rows = dt.Select("Instance>=" + (i - PeriodWeight.Length).ToString() + " AND Instance < " + i.ToString(), "Instance"); for (int j = 0; j < rows.Length; j++) {//apply an initial, uniform weight (1 / rows.Length) to the initial rows avg += (Decimal)rows[j]["Value"] * (1 / rows.Length); } row["Forecast"] = avg; } else if ((i < values.Length) && (i >= PeriodWeight.Length)) {//Out of initial rows and processing the test set decimal avg = 0; //Get the rows within the weight range just prior to the current row DataRow[] rows = dt.Select("Instance>=" + (i - PeriodWeight.Length).ToString() + " AND Instance < " + i.ToString(), "Instance"); for (int j = 0; j <= rows.Length - 1; j++) {//Apply the appropriate period's weight to the value avg += (Decimal)rows[j]["Value"] * PeriodWeight[j]; } //Assign the forecasted value to the current row row["Forecast"] = avg; } else {//into the extension decimal avg = 0; DataRow[] rows = dt.Select("Instance>=" + (i - PeriodWeight.Length).ToString() + " AND Instance < " + i.ToString(), "Instance"); for (int j = 0; j < rows.Length; j++) {//with no actual values to weight, use the previous rows' forecast instead avg += (Decimal)rows[j]["Forecast"] * PeriodWeight[j]; } row["Forecast"] = avg; } row.EndEdit(); } dt.AcceptChanges(); return dt; }