/// <summary> /// Initializes a new contingency table. /// </summary> /// <param name="rowValues">The low labels.</param> /// <param name="columnValues">The column labels.</param> public ContingencyTable(IReadOnlyCollection <R> rowValues, IReadOnlyCollection <C> columnValues) { if (rowValues == null) { throw new ArgumentNullException(nameof(rowValues)); } if (columnValues == null) { throw new ArgumentNullException(nameof(columnValues)); } rowMap = new NullableDictionary <R, int>(); foreach (R rowValue in rowValues) { rowMap.Add(rowValue, rowMap.Count); } columnMap = new NullableDictionary <C, int>(); foreach (C columnValue in columnValues) { columnMap.Add(columnValue, columnMap.Count); } counts = new int[rowMap.Count, columnMap.Count]; rCounts = new int[rowMap.Count]; cCounts = new int[columnMap.Count]; tCounts = 0; }
internal ContingencyTable(NullableDictionary <R, int> rowMap, NullableDictionary <C, int> columnMap, int[,] counts, int[] rCounts, int[] cCounts, int tCounts) { this.rowMap = rowMap; this.columnMap = columnMap; this.counts = counts; this.rCounts = rCounts; this.cCounts = cCounts; this.tCounts = tCounts; }
internal KeyCollection(NullableDictionary <K, V> parent) { this.parent = parent; }
/// <summary> /// Produces a cross-tabulation. /// </summary> /// <typeparam name="R">The type of row data.</typeparam> /// <typeparam name="C">The type of column data</typeparam> /// <param name="rowValues">The data values for rows.</param> /// <param name="columnValues">The data values for columns.</param> /// <returns>A cross-tabular summary of the number of joint occurrences of each row and column value.</returns> public static ContingencyTable <R, C> Crosstabs <R, C> (IReadOnlyList <R> rowValues, IReadOnlyList <C> columnValues) { if (rowValues == null) { throw new ArgumentNullException(nameof(rowValues)); } if (columnValues == null) { throw new ArgumentNullException(nameof(columnValues)); } if (rowValues.Count != columnValues.Count) { throw new DimensionMismatchException(); } // This is coded as a two passes over the data. The first pass determines // the distinct row and column values. The second pass determines the cell counts. // It's possible to do this in one pass, but we need auxiliary memory and/or // dynamic storage and the details become messier. // Determine the distinct row and column values NullableDictionary <R, int> rowMap = new NullableDictionary <R, int>(); NullableDictionary <C, int> columnMap = new NullableDictionary <C, int>(); for (int i = 0; i < rowValues.Count; i++) { R rowValue = rowValues[i]; C columnValue = columnValues[i]; if (!rowMap.ContainsKey(rowValue)) { rowMap.Add(rowValue, rowMap.Count); } if (!columnMap.ContainsKey(columnValue)) { columnMap.Add(columnValue, columnMap.Count); } } // Fill out the cells and marginal totals int[,] counts = new int[rowMap.Count, columnMap.Count]; int[] rowCounts = new int[rowMap.Count]; int[] columnCounts = new int[columnMap.Count]; int totalCount = 0; for (int i = 0; i < rowValues.Count; i++) { R rowValue = rowValues[i]; C columnValue = columnValues[i]; int rowIndex = rowMap[rowValue]; int columnIndex = columnMap[columnValue]; counts[rowIndex, columnIndex]++; rowCounts[rowIndex]++; columnCounts[columnIndex]++; totalCount++; } return(new ContingencyTable <R, C>(rowMap, columnMap, counts, rowCounts, columnCounts, totalCount)); }