/// <summary> /// Performs any analyses on this data object and writes the results to a space-delimitted /// file for subsequent copy-and-pasting into a spreadsheet like Microsoft Excel or SAS JMP. /// </summary> /// <param name="writer">An open stream writer pointed to a text file. The writer will be closed by /// this method after writing.</param> /// <returns>True if writing is successful; false otherwise.</returns> public bool WriteResultsToTxt(StreamWriter writer) { bool success = true; try { // write an identifying title for this file. writer.WriteLine("FittsStudy log analysis results for '{0}' on {1} at {2}. FittsStudy.exe version: {3}.\r\n", this.FilenameBase + ".xml", // Note: ((FileStream) writer.BaseStream).Name holds the file path. DateTime.Now.ToLongDateString(), DateTime.Now.ToLongTimeString(), Assembly.GetExecutingAssembly().GetName().Version); // write the column headers with comma separation -- see Columns.txt writer.WriteLine("Subject,Circular?,Block,Condition,Trial,Practice?,Metronome?,MT%,MTPred,MT,a(given),b(given),A,W,ID,axis,angle,ae(1d),dx(1d),ae(2d),dx(2d),Ae(1d),SD(1d),We(1d),IDe(1d),TP(1d),Ae(2d),SD(2d),We(2d),IDe(2d),TP(2d),MTe,MTRatio,MeanMTe,MeanMTe(sx),MeanMTe(tx),Entries,Overshoots,Error?,Errors,Errors(sx),Errors(tx),Error%,Error%(sx),Error%(tx),SpatialOutlier?,TemporalOutlier?,SpatialOutliers,TemporalOutliers,StartX,StartY,EndX,EndY,TargetX,TargetY,Travel,Duration,Submovements,MaxVelocity,MaxAcceleration,MaxJerk,tMaxVelocity,tMaxAcceleration,tMaxJerk,TAC,MDC,ODC,MV,ME,MO,N,Fitts_TP_avg(1d),Fitts_TP_inv(1d),Fitts_a(1d),Fitts_b(1d),Fitts_r(1d),Fitts_TP_avg(2d),Fitts_TP_inv(2d),Fitts_a(2d),Fitts_b(2d),Fitts_r(2d),Error_m(1d),Error_b(1d),Error_r(1d),Error_m(2d),Error_b(2d),Error_r(2d)"); // pre-compute session-level values here Model fm = this.BuildModel(); fm.RoundTerms(4); // now iterate through all of the conditions for (int i = 0; i < _conditions.Count; i++) { // get the condition and pre-compute any condition-level values here. we could // compute them again and again while writing each trial, but that is inefficient. ConditionData cd = _conditions[i]; double cAe_1d = Math.Round(cd.GetAe(false), 4); double cSD_1d = Math.Round(cd.GetSD(false), 4); double cWe_1d = Math.Round(cd.GetWe(false), 4); double cIDe_1d = Math.Round(cd.GetIDe(false), 4); double cTP_1d = Math.Round(cd.GetTP(false), 4); double cAe_2d = Math.Round(cd.GetAe(true), 4); double cSD_2d = Math.Round(cd.GetSD(true), 4); double cWe_2d = Math.Round(cd.GetWe(true), 4); double cIDe_2d = Math.Round(cd.GetIDe(true), 4); double cTP_2d = Math.Round(cd.GetTP(true), 4); long meanMTe = cd.GetMTe(ExcludeOutliersType.None); long meanMTe_sx = cd.GetMTe(ExcludeOutliersType.Spatial); long meanMTe_tx = cd.GetMTe(ExcludeOutliersType.Temporal); int errors = cd.GetNumErrors(ExcludeOutliersType.None); int errors_sx = cd.GetNumErrors(ExcludeOutliersType.Spatial); int errors_tx = cd.GetNumErrors(ExcludeOutliersType.Temporal); double errorPct = Math.Round(cd.GetErrorRate(ExcludeOutliersType.None), 4); double errorPct_sx = Math.Round(cd.GetErrorRate(ExcludeOutliersType.Spatial), 4); double errorPct_tx = Math.Round(cd.GetErrorRate(ExcludeOutliersType.Temporal), 4); int nSpatialOutliers = cd.NumSpatialOutliers; int nTemporalOutliers = cd.NumTemporalOutliers; // within each condition, iterate through the trials. start at index 1 because // the trial at index 0 is the special start-area trial, and should be ignored. for (int j = 1; j <= cd.NumTrials; j++) { TrialData td = cd[j]; MovementData md = td.Movement; // the movement path itself // calculate the resampled submovement profiles MovementData.Profiles profiles = md.CreateResampledProfiles(); int vidx = SeriesEx.Max(profiles.Velocity, 0, -1); // max velocity int aidx = SeriesEx.Max(profiles.Acceleration, 0, -1); // max acceleration int jidx = SeriesEx.Max(profiles.Jerk, 0, -1); // max jerk // calculate the MacKenzie et al. (2001) path analysis measures MovementData.PathAnalyses analyses = md.DoPathAnalyses(td.Start, td.TargetCenterFromStart); // write the spreadsheet row here writer.WriteLine("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},{17},{18},{19},{20},{21},{22},{23},{24},{25},{26},{27},{28},{29},{30},{31},{32},{33},{34},{35},{36},{37},{38},{39},{40},{41},{42},{43},{44},{45},{46},{47},{48},{49},{50},{51},{52},{53},{54},{55},{56},{57},{58},{59},{60},{61},{62},{63},{64},{65},{66},{67},{68},{69},{70},{71},{72},{73},{74},{75},{76},{77},{78},{79},{80},{81},{82},{83},{84},{85},{86}", _subject, // Subject, _circular ? 1 : 0, // Circular? cd.Block, // Block, cd.Index, // Condition, td.Number, // Trial, (== j) td.IsPractice ? 1 : 0, // Practice?, cd.UsedMetronome ? 1 : 0, // Metronome?, (== td.UsedMetronome) cd.MTPct, // MT%, cd.MTPred, // MTPred, cd.MT, // MT, (== td.MT) _intercept, // a(given), _slope, // b(given), cd.A, // A, (== td.A) cd.W, // W, (== td.W) Math.Round(cd.ID, 4), // ID, (== td.ID) Math.Round(GeotrigEx.Radians2Degrees(td.Axis), 4), // axis, Math.Round(GeotrigEx.Radians2Degrees(td.Angle), 4), // angle, Math.Round(td.GetAe(false), 4), // ae(1d), Math.Round(td.GetDx(false), 4), // dx(1d), Math.Round(td.GetAe(true), 4), // ae(2d), Math.Round(td.GetDx(true), 4), // dx(2d), cAe_1d, // Ae(1d), cSD_1d, // SD(1d), cWe_1d, // We(1d), cIDe_1d, // IDe(1d), cTP_1d, // TP(1d), cAe_2d, // Ae(2d), cSD_2d, // SD(2d), cWe_2d, // We(2d), cIDe_2d, // IDe(2d), cTP_2d, // TP(2d), td.MTe, // MTe, Math.Round(td.MTRatio, 4), // MTRatio, meanMTe, // MeanMTe, meanMTe_sx, // MeanMTe(sx), meanMTe_tx, // MeanMTe(tx), td.TargetEntries, // Entries, td.TargetOvershoots, // Overshoots, td.IsError ? 1 : 0, // Error?, errors, // Errors, errors_sx, // Errors(sx), errors_tx, // Errors(tx), errorPct, // Error%, errorPct_sx, // Error%(sx), errorPct_tx, // Error%(tx), td.IsSpatialOutlier ? 1 : 0, // SpatialOutlier?, td.IsTemporalOutlier ? 1 : 0, // TemporalOutlier?, nSpatialOutliers, // SpatialOutliers, nTemporalOutliers, // TemporalOutliers, td.Start.X, // StartX td.Start.Y, // StartY td.End.X, // EndX td.End.Y, // EndY td.TargetCenterFromStart.X, // TargetX td.TargetCenterFromStart.Y, // TargetY Math.Round(md.Travel, 4), // Travel, md.Duration, // Duration, md.GetNumSubmovements(), // Submovements, Math.Round(profiles.Velocity[vidx].Y, 4), // MaxVelocity, Math.Round(profiles.Acceleration[aidx].Y, 4), // MaxAcceleration, Math.Round(profiles.Jerk[jidx].Y, 4), // MaxJerk, Math.Round(profiles.Velocity[vidx].X / md.Duration, 4), // tMaxVelocity, Math.Round(profiles.Acceleration[aidx].X / md.Duration, 4), // tMaxAcceleration, Math.Round(profiles.Jerk[jidx].X / md.Duration, 4), // tMaxJerk, analyses.TaskAxisCrossings, // TAC, analyses.MovementDirectionChanges, // MDC, analyses.OrthogonalDirectionChanges, // ODC, Math.Round(analyses.MovementVariability, 4), // MV, Math.Round(analyses.MovementError, 4), // ME, Math.Round(analyses.MovementOffset, 4), // MO, fm.N, // N, fm.Fitts_TP_avg_1d, // Fitts_TP_avg(1d), fm.Fitts_TP_inv_1d, // Fitts_TP_inv(1d), fm.Fitts_a_1d, // Fitts_a(1d), fm.Fitts_b_1d, // Fitts_b(1d), fm.Fitts_r_1d, // Fitts_r(1d), fm.Fitts_TP_avg_2d, // Fitts_TP_avg(2d), fm.Fitts_TP_inv_2d, // Fitts_TP_inv(2d), fm.Fitts_a_2d, // Fitts_a(2d), fm.Fitts_b_2d, // Fitts_b(2d), fm.Fitts_r_2d, // Fitts_r(2d), fm.Error_m_1d, // Error_m(1d), fm.Error_b_1d, // Error_b(1d), fm.Error_r_1d, // Error_r(1d), fm.Error_m_2d, // Error_m(2d), fm.Error_b_2d, // Error_b(2d), fm.Error_r_2d // Error_r(2d) ); } } } catch (IOException ioex) { Console.WriteLine(ioex); success = false; } catch (Exception ex) { Console.WriteLine(ex); success = false; } finally { if (writer != null) { writer.Close(); } } return(success); }
/// <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); }