/// <summary> /// Constructs a FittsSession instance. A FittsSession contains conditions for a Fitts' law study, /// which, in turn, contain a set of trials. A constructed instance contains a list of conditions /// in sequence, which themselves contain a list of trials. /// </summary> /// <param name="o">The options that configure this session obtained from the OptionsForm dialog.</param> public SessionData(OptionsForm.Options o) { // // Set the condition variables that define this test. // _subject = o.Subject; _circular = o.Is2D; _a = o.A; _w = o.W; _mtpct = o.MTPct; _intercept = o.Intercept; _slope = o.Slope; _screen = Screen.PrimaryScreen.Bounds; _conditions = new List <ConditionData>(); // // Create the order of conditions. Nesting is mt%[a[w]]]. // int[] a_order, w_order, mt_order; if (o.Randomize) // randomize the order of conditions { a_order = RandomEx.Array(0, o.A.Length - 1, o.A.Length, true); w_order = RandomEx.Array(0, o.W.Length - 1, o.W.Length, true); if (o.MTPct != null) { mt_order = RandomEx.Array(0, o.MTPct.Length - 1, o.MTPct.Length, true); while (o.MTPct[mt_order[0]] < StatsEx.Mean(o.MTPct)) // enforce that the first MT% condition is >avg(MT%) { mt_order = RandomEx.Array(0, o.MTPct.Length - 1, o.MTPct.Length, true); } } else { mt_order = null; } } else // in-order arrays { a_order = new int[o.A.Length]; for (int i = 0; i < o.A.Length; i++) { a_order[i] = i; } w_order = new int[o.W.Length]; for (int i = 0; i < o.W.Length; i++) { w_order[i] = i; } mt_order = (o.MTPct != null) ? new int[o.MTPct.Length] : null; for (int i = 0; o.MTPct != null && i < o.MTPct.Length; i++) { mt_order[i] = i; } } // // Create the ordered condition list for the first block of all conditions (i.e., one time through). // int n = 0; for (int i = 0; o.MTPct == null || i < o.MTPct.Length; i++) { for (int j = 0; j < o.A.Length; j++) { for (int k = 0; k < o.W.Length; k++) { double pct = (o.MTPct != null) ? o.MTPct[mt_order[i]] : -1.0; // MT% double fitts = o.Intercept + o.Slope * Math.Log((double)o.A[a_order[j]] / o.W[w_order[k]] + 1.0, 2.0); long mt = (o.MTPct != null) ? (long)fitts : -1L; // Fitts' law predicted movement time ConditionData cd = new ConditionData(0, n++, o.A[a_order[j]], o.W[w_order[k]], pct, mt, o.Trials, o.Practice, o.Is2D); _conditions.Add(cd); } } if (o.MTPct == null) { break; } } // // Now possibly replicate the created order multiple times. // int nConditions = _conditions.Count; // number of conditions in one block for (int b = 1; b < o.Blocks; b++) { for (int c = 0; c < nConditions; c++) { ConditionData fx = _conditions[c]; ConditionData fc = new ConditionData(b, c, fx.A, fx.W, fx.MTPct, fx.MTPred, o.Trials, o.Practice, o.Is2D); _conditions.Add(fc); } } }
private Graph gphErr; // error rates /// <summary> /// /// </summary> /// <param name="sd"></param> public ResultsForm(SessionData sd, string filename) { InitializeComponent(); this.Text = filename; // create a graph for speed gphWpm = new Graph(); gphWpm.Title = "Speed"; gphWpm.XAxisName = "Trial No."; gphWpm.YAxisName = "WPM"; gphWpm.XAxisTicks = (sd.NumTrials > 1) ? sd.NumTrials - 1 : 1; gphWpm.XAxisDecimals = 1; gphWpm.YAxisDecimals = 1; gphWpm.Legend = true; // create a graph for error rates gphErr = new Graph(); gphErr.Title = "Error Rates"; gphErr.XAxisName = "Trial No."; gphErr.YAxisName = "Error Rate"; gphErr.XAxisTicks = (sd.NumTrials > 1) ? sd.NumTrials - 1 : 1; gphErr.XAxisDecimals = 1; gphErr.YAxisDecimals = 3; gphErr.Legend = true; // create each series we wish to graph Graph.Series sWpm = new Graph.Series("WPM", Color.Red, Color.Red, true, true); Graph.Series sAdj = new Graph.Series("AdjWPM", Color.Gray, Color.Gray, true, true); Graph.Series sTot = new Graph.Series("Total", Color.Red, Color.Red, true, true); Graph.Series sUnc = new Graph.Series("Uncorrected", Color.Gray, Color.Gray, true, true); Graph.Series sCor = new Graph.Series("Corrected", Color.LightGray, Color.LightGray, true, true); // use these lists to compute the means and stdevs for each series List <double> mWpm = new List <double>(); List <double> sdWpm = new List <double>(); List <double> mAdj = new List <double>(); List <double> sdAdj = new List <double>(); List <double> mTot = new List <double>(); List <double> sdTot = new List <double>(); List <double> mUnc = new List <double>(); List <double> sdUnc = new List <double>(); List <double> mCor = new List <double>(); List <double> sdCor = new List <double>(); // add the points for each series for (int i = 0; i < sd.NumTrials; i++) { if (sd[i].NumEntries > 0) { sWpm.AddPoint(new PointR(i + 1, sd[i].WPM)); mWpm.Add(sd[i].WPM); sdWpm.Add(sd[i].WPM); sAdj.AddPoint(new PointR(i + 1, sd[i].AdjWPM)); mAdj.Add(sd[i].AdjWPM); sdAdj.Add(sd[i].AdjWPM); sTot.AddPoint(new PointR(i + 1, sd[i].TotalErrorRate)); mTot.Add(sd[i].TotalErrorRate); sdTot.Add(sd[i].TotalErrorRate); sUnc.AddPoint(new PointR(i + 1, sd[i].UncorrectedErrorRate)); mUnc.Add(sd[i].UncorrectedErrorRate); sdUnc.Add(sd[i].UncorrectedErrorRate); sCor.AddPoint(new PointR(i + 1, sd[i].CorrectedErrorRate)); mCor.Add(sd[i].CorrectedErrorRate); sdCor.Add(sd[i].CorrectedErrorRate); } } // add the means and standard deviations to the series' names sWpm.Name = String.Format("{0} (µ={1:f1}, σ={2:f1})", sWpm.Name, StatsEx.Mean(mWpm.ToArray()), StatsEx.StdDev(sdWpm.ToArray())); sAdj.Name = String.Format("{0} (µ={1:f1}, σ={2:f1})", sAdj.Name, StatsEx.Mean(mAdj.ToArray()), StatsEx.StdDev(sdAdj.ToArray())); sTot.Name = String.Format("{0} (µ={1:f3}, σ={2:f3})", sTot.Name, StatsEx.Mean(mTot.ToArray()), StatsEx.StdDev(sdTot.ToArray())); sUnc.Name = String.Format("{0} (µ={1:f3}, σ={2:f3})", sUnc.Name, StatsEx.Mean(mUnc.ToArray()), StatsEx.StdDev(sdUnc.ToArray())); sCor.Name = String.Format("{0} (µ={1:f3}, σ={2:f3})", sCor.Name, StatsEx.Mean(mCor.ToArray()), StatsEx.StdDev(sdCor.ToArray())); // add the origin point to the graphs Graph.Series origin = new Graph.Series(String.Empty, Color.Black, Color.Black, false, false); origin.AddPoint(1.0, 0.0); gphWpm.AddSeries(origin); gphErr.AddSeries(origin); // finally, add the series gphWpm.AddSeries(sWpm); gphWpm.AddSeries(sAdj); gphErr.AddSeries(sTot); gphErr.AddSeries(sUnc); gphErr.AddSeries(sCor); }
/// <summary> /// Performs linear regression on the entire session's worth of data to fit a Fitts' law model /// of the form MTe = a + b * log2(Ae/We + 1). /// </summary> /// <returns>Model and fitting parameters.</returns> public Model BuildModel() { Model model = Model.Empty; model.FittsPts_1d = new List <PointF>(_conditions.Count); model.FittsPts_2d = new List <PointF>(_conditions.Count); double[] ide = new double[_conditions.Count]; // observed index of difficulties for each condition double[] mte = new double[_conditions.Count]; // observed mean movement times for each condition double[] tp = new double[_conditions.Count]; // observed throughputs for each condition for (int i = 0; i <= 1; i++) // first loop is univariate, second loop is bivariate { for (int j = 0; j < _conditions.Count; j++) // each A x W or MT% x A x W condition (blocks kept separate) { ConditionData cdata = _conditions[j]; ide[j] = cdata.GetIDe(i == 1); // bits mte[j] = cdata.GetMTe(ExcludeOutliersType.Spatial); // ms tp[j] = cdata.GetTP(i == 1); // bits/s if (i == 0) { model.FittsPts_1d.Add(new PointF((float)ide[j], (float)mte[j])); // for graphing later } else { model.FittsPts_2d.Add(new PointF((float)ide[j], (float)mte[j])); // for graphing later } } if (i == 0) // univariate { model.N = _conditions.Count; // == model.FittsPts.Count model.MTe = StatsEx.Mean(mte); // ms model.Fitts_TP_avg_1d = StatsEx.Mean(tp); // bits/s model.Fitts_a_1d = StatsEx.Intercept(ide, mte); // ms model.Fitts_b_1d = StatsEx.Slope(ide, mte); // ms/bit model.Fitts_TP_inv_1d = 1.0 / model.Fitts_b_1d * 1000.0; // bits/s model.Fitts_r_1d = StatsEx.Pearson(ide, mte); // correlation } else // bivariate { model.Fitts_TP_avg_2d = _circular ? StatsEx.Mean(tp) : 0.0; // bits/s model.Fitts_a_2d = _circular ? StatsEx.Intercept(ide, mte) : 0.0; // ms model.Fitts_b_2d = _circular ? StatsEx.Slope(ide, mte) : 0.0; // ms/bit model.Fitts_TP_inv_2d = _circular ? 1.0 / model.Fitts_b_2d * 1000.0 : 0.0; // bits/s model.Fitts_r_2d = _circular ? StatsEx.Pearson(ide, mte) : 0.0; // correlation } } // // Now compute the predicted error rates relevant to metronome experiments. // model.ErrorPts_1d = new List <PointF>(_conditions.Count); model.ErrorPts_2d = new List <PointF>(_conditions.Count); double[] errPred = new double[_conditions.Count]; // x, predicted errors based on the error model for pointing double[] errObs = new double[_conditions.Count]; // y, observed error rates for each condition (spatial outliers included) for (int i = 0; i <= 1; i++) // first loop is univariate, second loop is bivariate { for (int j = 0; j < _conditions.Count; j++) // each MT% x A x W condition, duplicates left separate { ConditionData cdata = _conditions[j]; errObs[j] = cdata.GetErrorRate(ExcludeOutliersType.Temporal); // observed error rates -- temporal outliers excluded double mt = cdata.GetMTe(ExcludeOutliersType.Temporal); // observed movement times -- temporal outliers excluded double z = (i == 0) ? 2.066 * ((double)cdata.W / cdata.A) * (Math.Pow(2.0, (mt - model.Fitts_a_1d) / model.Fitts_b_1d) - 1.0) : 2.066 * ((double)cdata.W / cdata.A) * (Math.Pow(2.0, (mt - model.Fitts_a_2d) / model.Fitts_b_2d) - 1.0); errPred[j] = GeotrigEx.PinValue(2.0 * (1.0 - StatsEx.CDF(0.0, 1.0, z)), 0.0, 1.0); // predicted error rates == 1 - erf(z / sqrt(2)) //double shah = 1.0 - (2.0 * StatsEx.ShahNormal(z)); // Shah approximation of the standard normal distribution if (i == 0) { model.ErrorPts_1d.Add(new PointF((float)errPred[j], (float)errObs[j])); // for graphing later } else { model.ErrorPts_2d.Add(new PointF((float)errPred[j], (float)errObs[j])); // for graphing later } } if (i == 0) // univariate { model.ErrorPct = StatsEx.Mean(errObs); // percent model.Error_m_1d = StatsEx.Slope(errPred, errObs); model.Error_b_1d = StatsEx.Intercept(errPred, errObs); model.Error_r_1d = StatsEx.Pearson(errPred, errObs); } else // bivariate { model.Error_m_2d = _circular ? StatsEx.Slope(errPred, errObs) : 0.0; model.Error_b_2d = _circular ? StatsEx.Intercept(errPred, errObs) : 0.0; model.Error_r_2d = _circular ? StatsEx.Pearson(errPred, errObs) : 0.0; } } return(model); }