Confusion.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. /* Confusion.cpp
  2. *
  3. * Copyright (C) 1993-2018 David Weenink
  4. *
  5. * This code is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or (at
  8. * your option) any later version.
  9. *
  10. * This code is distributed in the hope that it will be useful, but
  11. * WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. * General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this work. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /*
  19. djmw 20010628
  20. djmw 20020813 GPL header
  21. djmw 20061214 Changed info to Melder_writeLine<x> format.
  22. djmw 20070620 Latest modification.
  23. djmw 20080521 +Confusion_drawAsNumbers
  24. djmw 20110304 Thing_new
  25. djmw 20111110 Use autostringvector
  26. */
  27. #include "Confusion.h"
  28. #include "Polygon_extensions.h"
  29. #include "Matrix_extensions.h"
  30. #include "TableOfReal_extensions.h"
  31. #include "Collection_extensions.h"
  32. #include "Distributions_and_Strings.h"
  33. #include "NUM2.h"
  34. Thing_implement (Confusion, TableOfReal, 0);
  35. void structConfusion :: v_info () {
  36. double h, hx, hy, hygx, hxgy, uygx, uxgy, uxy, frac;
  37. integer nCorrect;
  38. Confusion_getEntropies (this, & h, & hx, & hy, & hygx, & hxgy, & uygx, & uxgy, & uxy);
  39. Confusion_getFractionCorrect (this, & frac, & nCorrect);
  40. MelderInfo_writeLine (U"Number of rows: ", numberOfRows);
  41. MelderInfo_writeLine (U"Number of colums: ", numberOfColumns);
  42. MelderInfo_writeLine (U"Entropies (y is row variable):");
  43. MelderInfo_writeLine (U" Total: ", h);
  44. MelderInfo_writeLine (U" Y: ", hy);
  45. MelderInfo_writeLine (U" X: ", hx);
  46. MelderInfo_writeLine (U" Y given x: ", hygx);
  47. MelderInfo_writeLine (U" X given y: ", hxgy);
  48. MelderInfo_writeLine (U" Dependency of y on x; ", uygx);
  49. MelderInfo_writeLine (U" Dependency of x on y: ", uxgy);
  50. MelderInfo_writeLine (U" Symmetrical dependency: ", uxy);
  51. MelderInfo_writeLine (U" Total number of entries: ", Confusion_getNumberOfEntries (this));
  52. MelderInfo_writeLine (U" Fraction correct: ", frac);
  53. }
  54. autoConfusion Confusion_createFromStringses (Strings me, Strings thee) {
  55. try {
  56. Melder_require (my numberOfStrings > 0 && thy numberOfStrings > 0, U"Both Strings should not be empty.");
  57. autoConfusion him = Confusion_create (my numberOfStrings, thy numberOfStrings);
  58. for (integer irow = 1; irow <= my numberOfStrings; irow ++) {
  59. conststring32 label = my strings [irow].get();
  60. TableOfReal_setRowLabel (him.get(), irow, label);
  61. }
  62. for (integer icol = 1; icol <= thy numberOfStrings; icol ++) {
  63. conststring32 label = thy strings [icol].get();
  64. TableOfReal_setColumnLabel (him.get(), icol, label);
  65. }
  66. return him;
  67. } catch (MelderError) {
  68. Melder_throw (me, U": could not create Confusion with ", thee);
  69. }
  70. }
  71. autoConfusion Confusion_create (integer numberOfStimuli, integer numberOfResponses) {
  72. try {
  73. autoConfusion me = Thing_new (Confusion);
  74. TableOfReal_init (me.get(), numberOfStimuli, numberOfResponses);
  75. return me;
  76. } catch (MelderError) {
  77. Melder_throw (U"Confusion not created.");
  78. }
  79. }
  80. autoConfusion Confusion_createSimple (conststring32 labels_string) {
  81. try {
  82. autostring32vector labels = STRVECtokenize (labels_string);
  83. Melder_require (labels.size > 0, U"There should be at least one label.");
  84. autoConfusion me = Confusion_create (labels.size, labels.size);
  85. integer ilabel = 1;
  86. for (integer itoken = 1; itoken <= labels.size; itoken ++) {
  87. conststring32 token = labels [itoken].get();
  88. for (integer i = 1; i <= ilabel - 1; i ++) {
  89. if (Melder_equ (token, my rowLabels [i].get())) {
  90. Melder_throw (U"Label ", i, U" and ", ilabel, U" should not be equal.");
  91. }
  92. }
  93. TableOfReal_setRowLabel (me.get(), ilabel, token);
  94. TableOfReal_setColumnLabel (me.get(), ilabel, token);
  95. ilabel ++;
  96. }
  97. return me;
  98. } catch (MelderError) {
  99. Melder_throw (U"Simple Confusion not created.");
  100. }
  101. }
  102. autoConfusion Categories_to_Confusion (Categories me, Categories thee) {
  103. try {
  104. Melder_require (my size == thy size, U"Both Categories should have the same number of items.");
  105. autoCategories ul1 = Categories_selectUniqueItems (me);
  106. autoCategories ul2 = Categories_selectUniqueItems (thee);
  107. autoConfusion him = Confusion_create (ul1->size, ul2->size);
  108. for (integer i = 1; i <= ul1->size; i ++) {
  109. SimpleString s = ul1->at [i];
  110. TableOfReal_setRowLabel (him.get(), i, s -> string.get());
  111. }
  112. for (integer i = 1; i <= ul2->size; i ++) {
  113. SimpleString s = ul2->at [i];
  114. TableOfReal_setColumnLabel (him.get(), i, s -> string.get());
  115. }
  116. for (integer i = 1; i <= my size; i ++) {
  117. SimpleString myi = my at [i], thyi = thy at [i];
  118. Confusion_increase (him.get(), myi -> string.get(), thyi -> string.get());
  119. }
  120. return him;
  121. } catch (MelderError) {
  122. Melder_throw (me, U": no Confusion created.");
  123. }
  124. }
  125. void Confusion_getEntropies (Confusion me, double *out_h, double *out_hx, double *out_hy,
  126. double *out_hygx, double *out_hxgy, double *out_uygx, double *out_uxgy, double *out_uxy)
  127. {
  128. MAT_getEntropies (my data.get(), out_h, out_hx,
  129. out_hy, out_hygx, out_hxgy, out_uygx, out_uxgy, out_uxy);
  130. }
  131. void Confusion_increase (Confusion me, conststring32 stimulus, conststring32 response) {
  132. try {
  133. integer stimulusIndex = TableOfReal_rowLabelToIndex (me, stimulus);
  134. Melder_require (stimulusIndex > 0, U"The stimulus name should be valid.");
  135. integer responseIndex = TableOfReal_columnLabelToIndex (me, response);
  136. Melder_require (responseIndex > 0, U"The response name should be valid.");
  137. my data [stimulusIndex] [responseIndex] += 1.0;
  138. } catch (MelderError) {
  139. Melder_throw (me, U": not increased.");
  140. }
  141. }
  142. double Confusion_getValue (Confusion me, conststring32 stimulus, conststring32 response) {
  143. integer stimulusIndex = TableOfReal_rowLabelToIndex (me, stimulus);
  144. Melder_require (stimulusIndex > 0, U"The stimulus name should be valid.");
  145. integer responseIndex = TableOfReal_columnLabelToIndex (me, response);
  146. Melder_require (responseIndex > 0, U"The response name should be valid.");
  147. return my data [stimulusIndex] [responseIndex];
  148. }
  149. void Confusion_getFractionCorrect (Confusion me, double *out_fraction, integer *out_numberOfCorrect) {
  150. double fraction = undefined;
  151. integer numberOfCorrect = -1;
  152. double c = 0.0, ct = 0.0;
  153. for (integer i = 1; i <= my numberOfRows; i ++) {
  154. for (integer j = 1; j <= my numberOfColumns; j ++) {
  155. if (! my rowLabels [i] || ! my columnLabels [j])
  156. return;
  157. ct += my data [i] [j];
  158. if (str32equ (my rowLabels [i].get(), my columnLabels [j].get()))
  159. c += my data [i] [j];
  160. }
  161. }
  162. if (ct != 0.0)
  163. fraction = c / ct;
  164. if (out_fraction)
  165. *out_fraction = fraction;
  166. numberOfCorrect = Melder_ifloor (c);
  167. if (out_numberOfCorrect)
  168. *out_numberOfCorrect = numberOfCorrect;
  169. }
  170. /*************** Confusion_Matrix_draw ****************************************/
  171. #define NPOINTS 6
  172. static autoPolygon Polygon_createPointer () {
  173. try {
  174. double x [NPOINTS + 1] = { 0.0, 0.0, 0.9, 1.0, 0.9, 0.0, 0.0 };
  175. double y [NPOINTS + 1] = { 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 0.0 };
  176. autoPolygon me = Polygon_create (NPOINTS);
  177. for (integer i = 1; i <= NPOINTS; i ++) {
  178. my x [i] = x [i];
  179. my y [i] = y [i];
  180. }
  181. return me;
  182. } catch (MelderError) {
  183. Melder_throw (U"Polygon not created.");
  184. }
  185. }
  186. static void Polygon_drawInside (Polygon me, Graphics g) {
  187. Graphics_polyline (g, my numberOfPoints, & my x[1], & my y[1]);
  188. }
  189. void Confusion_Matrix_draw (Confusion me, Matrix thee, Graphics g, integer index, double lowerPercentage, double xmin, double xmax, double ymin, double ymax, int garnish) {
  190. integer ib = 1, ie = my numberOfRows;
  191. if (index > 0 && index <= my numberOfColumns) {
  192. ib = ie = index;
  193. }
  194. Melder_require (thy ny == my numberOfRows, U"The number of stimuli should equal the number of rows in the matrix.");
  195. if (xmax <= xmin)
  196. (void) Matrix_getWindowExtrema (thee, 1, 1, 1, thy ny, & xmin, & xmax);
  197. if (xmax <= xmin)
  198. return;
  199. if (ymax <= ymin)
  200. (void) Matrix_getWindowExtrema (thee, 2, 2, 1, thy ny, & ymin, & ymax);
  201. if (ymax <= ymin)
  202. return;
  203. double rmax = fabs (xmax - xmin) / 10.0;
  204. double rmin = rmax / 10;
  205. Graphics_setInner (g);
  206. Graphics_setWindow (g, xmin - rmax, xmax + rmax, ymin - rmax, ymax + rmax);
  207. Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_HALF);
  208. for (integer i = 1; i <= my numberOfRows; i ++) {
  209. Graphics_text (g, thy z [i] [1], thy z [i] [2], my rowLabels [i].get());
  210. }
  211. for (integer i = ib; i <= ie; i ++) {
  212. double xSum = 0.0;
  213. for (integer j = 1; j <= my numberOfColumns; j ++) {
  214. xSum += my data [i] [j];
  215. }
  216. if (xSum <= 0.0) {
  217. continue; /* no confusions */
  218. }
  219. double x1 = thy z [i] [1];
  220. double y1 = thy z [i] [2];
  221. double r = rmax * my data [i] [i] / xSum;
  222. Graphics_circle (g, x1, y1, r > rmin ? r : rmin);
  223. for (integer j = 1; j <= my numberOfColumns; j ++) {
  224. double x2 = thy z [j] [1], y2 = thy z [j] [2];
  225. double perc = 100.0 * my data [i] [j] / xSum;
  226. double dx = x2 - x1, dy = y2 - y1;
  227. double alpha = atan2 (dy, dx);
  228. if (perc == 0.0 || perc < lowerPercentage || j == i) {
  229. continue;
  230. }
  231. xmin = x1;
  232. xmax = x2;
  233. if (x2 < x1) {
  234. xmin = x2;
  235. xmax = x1;
  236. }
  237. ymin = y1;
  238. xmax = y2;
  239. if (y2 < y1) {
  240. ymin = y2;
  241. ymax = y1;
  242. }
  243. autoPolygon p = Polygon_createPointer();
  244. double xs = sqrt (dx * dx + dy * dy) - 2.2 * r;
  245. if (xs < 0.0)
  246. xs = 0.0;
  247. double ys = perc * rmax / 100.0;
  248. Polygon_scale (p.get(), xs, ys);
  249. Polygon_translate (p.get(), x1, y1 - ys / 2);
  250. Polygon_rotate (p.get(), alpha, x1, y1);
  251. Polygon_translate (p.get(), 1.1 * r * cos (alpha), 1.1 * r * sin (alpha));
  252. Polygon_drawInside (p.get(), g);
  253. }
  254. }
  255. Graphics_unsetInner (g);
  256. if (garnish) {
  257. Graphics_drawInnerBox (g);
  258. Graphics_marksBottom (g, 2, true, true, false);
  259. if (ymin * ymax < 0.0) {
  260. Graphics_markLeft (g, 0.0, true, true, true, nullptr);
  261. }
  262. Graphics_marksLeft (g, 2, true, true, false);
  263. if (xmin * xmax < 0.0) {
  264. Graphics_markBottom (g, 0.0, true, true, true, nullptr);
  265. }
  266. }
  267. }
  268. autoMatrix Confusion_difference (Confusion me, Confusion thee) {
  269. try {
  270. /* categories must be the same too*/
  271. Melder_require (my numberOfColumns == thy numberOfColumns && my numberOfRows == thy numberOfRows,
  272. U"The dimensions should be equal.");
  273. autoMatrix him = Matrix_create (0.5, my numberOfColumns + 0.5, my numberOfColumns, 1.0, 1.0, 0.5, my numberOfRows + 0.5, my numberOfRows, 1.0, 1.0);
  274. for (integer i = 1; i <= my numberOfRows; i ++) {
  275. for (integer j = 1; j <= my numberOfColumns; j ++) {
  276. his z [i] [j] = my data [i] [j] - thy data [i] [j];
  277. }
  278. }
  279. return him;
  280. } catch (MelderError) {
  281. Melder_throw (U"Matrix not created from two Confusions.");
  282. }
  283. }
  284. integer Confusion_getNumberOfEntries (Confusion me) {
  285. longdouble total = 0.0;
  286. for (integer i = 1; i <= my numberOfRows; i ++) {
  287. for (integer j = 1; j <= my numberOfColumns; j ++) {
  288. total += my data [i] [j];
  289. }
  290. }
  291. return Melder_ifloor ((double) total);
  292. }
  293. static void create_index (string32vector s, string32vector ref, integer index []) {
  294. for (integer i = 1; i <= s.size; i ++) {
  295. integer indxj = 0;
  296. for (integer j = 1; j <= ref.size; j ++) {
  297. if (str32equ (s [i], ref [j])) {
  298. indxj = j;
  299. break;
  300. }
  301. }
  302. index [i] = indxj;
  303. }
  304. }
  305. autoConfusion Confusion_condense (Confusion me, conststring32 search, conststring32 replace,
  306. integer maximumNumberOfReplaces, bool use_regexp) {
  307. try {
  308. integer nmatches, nstringmatches;
  309. Melder_require (my rowLabels && my columnLabels, U"Both row and column labels should be present.");
  310. autostring32vector rowLabels = string32vector_searchAndReplace (my rowLabels.get(),
  311. search, replace, maximumNumberOfReplaces, & nmatches, & nstringmatches, use_regexp);
  312. autostring32vector columnLabels = string32vector_searchAndReplace (my columnLabels.get(),
  313. search, replace, maximumNumberOfReplaces, & nmatches, & nstringmatches, use_regexp);
  314. autoStrings srow = Thing_new (Strings);
  315. srow -> numberOfStrings = my numberOfRows;
  316. srow -> strings = std::move (rowLabels);
  317. autoStrings scol = Thing_new (Strings);
  318. scol -> numberOfStrings = my numberOfColumns;
  319. scol -> strings = std::move (columnLabels);
  320. /* Find dimension of new Confusion */
  321. autoDistributions dcol = Strings_to_Distributions (scol.get());
  322. integer nresp = dcol -> numberOfRows;
  323. autoDistributions drow = Strings_to_Distributions (srow.get());
  324. integer nstim = drow -> numberOfRows;
  325. autoConfusion thee = Confusion_create (nstim, nresp);
  326. thy rowLabels. copyElementsFrom (drow -> rowLabels.get());
  327. thy columnLabels. copyElementsFrom (dcol -> rowLabels.get());
  328. autoNUMvector<integer> rowIndex (1, my numberOfRows);
  329. create_index (srow -> strings.get(), drow -> rowLabels.get(), rowIndex.peek());
  330. autoNUMvector<integer> columnIndex (1, my numberOfColumns);
  331. create_index (scol -> strings.get(), dcol -> rowLabels.get(), columnIndex.peek());
  332. for (integer i = 1; i <= my numberOfRows; i ++) {
  333. for (integer j = 1; j <= my numberOfColumns; j ++) {
  334. thy data [rowIndex [i]] [columnIndex [j]] += my data [i] [j];
  335. }
  336. }
  337. return thee;
  338. } catch (MelderError) {
  339. Melder_throw (me, U": not condensed.");
  340. }
  341. }
  342. autoConfusion TableOfReal_to_Confusion (TableOfReal me) {
  343. try {
  344. Melder_require (TableOfReal_checkNonNegativity (me), U"Elements should not be negative.");
  345. autoConfusion thee = Thing_new (Confusion);
  346. my structTableOfReal :: v_copy (thee.get());
  347. return thee;
  348. } catch (MelderError) {
  349. Melder_throw (me, U": not converted to Confusion.");
  350. }
  351. }
  352. autoConfusion Confusion_group (Confusion me, conststring32 labels, conststring32 newLabel, integer newpos) {
  353. try {
  354. autoConfusion stim = Confusion_groupStimuli (me, labels, newLabel, newpos);
  355. autoConfusion thee = Confusion_groupResponses (stim.get(), labels, newLabel, newpos);
  356. return thee;
  357. } catch (MelderError) {
  358. Melder_throw (me, U": not grouped.");
  359. }
  360. }
  361. autoConfusion Confusion_groupStimuli (Confusion me, conststring32 labels_string, conststring32 newLabel, integer newpos) {
  362. try {
  363. autostring32vector labels = STRVECtokenize (labels_string);
  364. integer ncondense = labels.size;
  365. autoNUMvector<integer> irow (1, my numberOfRows);
  366. for (integer i = 1; i <= my numberOfRows; i ++)
  367. irow [i] = i;
  368. for (integer itoken = 1; itoken <= labels.size; itoken ++) {
  369. conststring32 token = labels [itoken].get();
  370. for (integer i = 1; i <= my numberOfRows; i ++) {
  371. if (Melder_equ (token, my rowLabels [i].get())) {
  372. irow [i] = 0;
  373. break;
  374. }
  375. }
  376. }
  377. integer nfound = 0;
  378. for (integer i = 1; i <= my numberOfRows; i ++) {
  379. if (irow [i] == 0)
  380. nfound ++;
  381. }
  382. Melder_require (nfound > 0, U"The stimulus labels are invalid.");
  383. if (nfound != ncondense)
  384. Melder_warning (U"One or more of the given stimulus labels are suspect.");
  385. integer newnstim = my numberOfRows - nfound + 1;
  386. if (newpos < 1)
  387. newpos = 1;
  388. if (newpos > newnstim)
  389. newpos = newnstim;
  390. autoConfusion thee = Confusion_create (newnstim, my numberOfColumns);
  391. thy columnLabels. copyElementsFrom (my columnLabels.get());
  392. TableOfReal_setRowLabel (thee.get(), newpos, newLabel);
  393. integer inewrow = 1;
  394. for (integer i = 1; i <= my numberOfRows; i ++) {
  395. integer rowpos = newpos;
  396. if (irow [i] > 0) {
  397. if (inewrow == newpos)
  398. inewrow ++;
  399. rowpos = inewrow;
  400. inewrow ++;
  401. TableOfReal_setRowLabel (thee.get(), rowpos, my rowLabels [i].get());
  402. }
  403. for (integer j = 1; j <= my numberOfColumns; j ++)
  404. thy data [rowpos] [j] += my data [i] [j];
  405. }
  406. return thee;
  407. } catch (MelderError) {
  408. Melder_throw (me, U": stimuli not grouped.");
  409. }
  410. }
  411. autoConfusion Confusion_groupResponses (Confusion me, conststring32 labels_string, conststring32 newLabel, integer newpos) {
  412. try {
  413. autostring32vector labels = STRVECtokenize (labels_string);
  414. integer ncondense = labels.size;
  415. autoNUMvector<integer> icol (1, my numberOfColumns);
  416. for (integer i = 1; i <= my numberOfColumns; i ++)
  417. icol [i] = i;
  418. for (integer itoken = 1; itoken <= labels.size; itoken ++) {
  419. conststring32 token = labels [itoken].get();
  420. for (integer i = 1; i <= my numberOfColumns; i ++) {
  421. if (Melder_equ (token, my columnLabels [i].get())) {
  422. icol [i] = 0;
  423. break;
  424. }
  425. }
  426. }
  427. integer nfound = 0;
  428. for (integer i = 1; i <= my numberOfColumns; i ++) {
  429. if (icol [i] == 0)
  430. nfound ++;
  431. }
  432. Melder_require (nfound > 0, U"The response labels are invalid.");
  433. if (nfound != ncondense)
  434. Melder_warning (U"One or more of the given response labels are suspect.");
  435. integer newnresp = my numberOfColumns - nfound + 1;
  436. if (newpos < 1)
  437. newpos = 1;
  438. if (newpos > newnresp)
  439. newpos = newnresp;
  440. autoConfusion thee = Confusion_create (my numberOfRows, newnresp);
  441. thy rowLabels. copyElementsFrom (my rowLabels.get());
  442. TableOfReal_setColumnLabel (thee.get(), newpos, newLabel);
  443. integer inewcol = 1;
  444. for (integer i = 1; i <= my numberOfColumns; i ++) {
  445. integer colpos = newpos;
  446. if (icol [i] > 0) {
  447. if (inewcol == newpos)
  448. inewcol ++;
  449. colpos = inewcol;
  450. inewcol ++;
  451. TableOfReal_setColumnLabel (thee.get(), colpos, my columnLabels [i].get());
  452. }
  453. for (integer j = 1; j <= my numberOfRows; j ++)
  454. thy data [j] [colpos] += my data [j] [i];
  455. }
  456. return thee;
  457. } catch (MelderError) {
  458. Melder_throw (me, U": responses not grouped.");
  459. }
  460. }
  461. autoTableOfReal Confusion_to_TableOfReal_marginals (Confusion me) {
  462. try {
  463. autoTableOfReal thee = TableOfReal_create (my numberOfRows + 1, my numberOfColumns + 1);
  464. longdouble total = 0.0;
  465. for (integer i = 1; i <= my numberOfRows; i ++) {
  466. longdouble rowSum = 0.0;
  467. for (integer j = 1; j <= my numberOfColumns; j ++) {
  468. thy data [i] [j] = my data [i] [j];
  469. rowSum += my data [i] [j];
  470. }
  471. thy data [i] [my numberOfColumns + 1] = (double) rowSum;
  472. total += rowSum;
  473. }
  474. thy data [my numberOfRows + 1] [my numberOfColumns + 1] = (double) total;
  475. for (integer j = 1; j <= my numberOfColumns; j ++) {
  476. longdouble columnSum = 0.0;
  477. for (integer i = 1; i <= my numberOfRows; i ++)
  478. columnSum += my data [i] [j];
  479. thy data [my numberOfRows + 1] [j] = (double) columnSum;
  480. }
  481. thy rowLabels. copyElementsFrom_upTo (my rowLabels.get(), my numberOfRows);
  482. thy columnLabels. copyElementsFrom_upTo (my columnLabels.get(), my numberOfColumns);
  483. return thee;
  484. } catch (MelderError) {
  485. Melder_throw (me, U": table with marginals not created.");
  486. }
  487. }
  488. void Confusion_drawAsNumbers (Confusion me, Graphics g, bool marginals, int iformat, int precision) {
  489. TableOfReal thee = me;
  490. autoTableOfReal athee;
  491. if (marginals) {
  492. athee = Confusion_to_TableOfReal_marginals (me);
  493. thee = athee.get();
  494. }
  495. TableOfReal_drawAsNumbers (thee, g, 1, thy numberOfRows, iformat, precision);
  496. }
  497. /* End of file Confusion.cpp */