Пример #1
0
        /// <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);
                }
            }
        }
Пример #2
0
        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);
        }
Пример #3
0
        /// <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);
        }