source: orange/source/corn/corn.cpp @ 7668:0f13cd5d7ca6

Revision 7668:0f13cd5d7ca6, 23.1 KB checked in by janezd <janez.demsar@…>, 3 years ago (diff)

Added PyErr_Clear after PyObject_GetAttrString when trying camelCase and underscore variations

Line 
1/*
2    This file is part of Orange.
3   
4    Copyright 1996-2010 Faculty of Computer and Information Science, University of Ljubljana
5    Contact: janez.demsar@fri.uni-lj.si
6
7    Orange is free software: you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation, either version 3 of the License, or
10    (at your option) any later version.
11
12    Orange is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with Orange.  If not, see <http://www.gnu.org/licenses/>.
19*/
20
21
22#ifdef _MSC_VER
23  #define NOMINMAX
24  #define WIN32_LEAN_AND_MEAN       // Exclude rarely-used stuff from Windows headers
25  #include <windows.h>
26#endif
27
28#include "c2py.hpp"
29#include "pywrapper.hpp"
30#include "stladdon.hpp"
31#include <vector>
32#include <map>
33#include <utility>
34#include <algorithm>
35#include <string>
36using namespace std;
37
38#include "corn.hpp"
39#include "c2py.hpp"
40
41/* *********** MODULE INITIALIZATION ************/
42
43#define PyTRY try {
44
45#define PYNULL ((PyObject *)NULL)
46#define PyCATCH   PyCATCH_r(PYNULL)
47#define PyCATCH_1 PyCATCH_r(-1)
48
49#define PyCATCH_r(r) \
50  } \
51catch (pyexception err)   { err.restore(); return r; } \
52catch (exception err) { PYERROR(PyExc_CornKernel, err.what(), r); }
53
54
55PyObject *PyExc_CornKernel;
56PyObject *PyExc_CornWarning;
57
58void initcorn()
59{ if (   ((PyExc_CornKernel = makeExceptionClass("corn.KernelException", "An error occurred in corn's C++ code")) == NULL)
60      || ((PyExc_CornWarning = makeExceptionClass("corn.Warning", "corn warning", PyExc_Warning)) == NULL))
61    return;
62
63  PyObject *me;
64  me = Py_InitModule("corn", corn_functions);
65}
66
67
68#ifdef _MSC_VER
69BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
70{ switch (ul_reason_for_call)
71    { case DLL_PROCESS_ATTACH:case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break; }
72  return TRUE;
73}
74#endif
75
76/* *********** EXCEPTION CATCHING ETC. ************/
77
78
79#include <exception>
80#include <string>
81
82using namespace std;
83
84#ifdef _MSC_VER
85
86#define cornexception exception
87
88#else
89
90class cornexception : public exception {
91public:
92   string err_desc;
93   cornexception(const string &desc)
94   : err_desc(desc)
95   {}
96
97   ~cornexception() throw()
98   {}
99
100   virtual const char* what () const throw()
101   { return err_desc.c_str(); };
102};
103
104#endif
105
106
107exception CornException(const string &anerr)
108{ return cornexception(anerr.c_str()); }
109
110exception CornException(const string &anerr, const string &s)
111{ char buf[255];
112  sprintf(buf, anerr.c_str(), s.c_str());
113  return cornexception(buf);
114}
115
116exception CornException(const string &anerr, const string &s1, const string &s2)
117{ char buf[255];
118  sprintf(buf, anerr.c_str(), s1.c_str(), s2.c_str());
119  return cornexception(buf);
120}
121
122exception CornException(const string &anerr, const string &s1, const string &s2, const string &s3)
123{ char buf[255];
124  sprintf(buf, anerr.c_str(), s1.c_str(), s2.c_str(), s3.c_str());
125  return cornexception(buf);
126}
127
128exception CornException(const string &anerr, const long i)
129{ char buf[255];
130  sprintf(buf, anerr.c_str(), i);
131  return cornexception(buf);
132}
133
134#undef min
135#undef max
136
137#define PyTRY try {
138#define PYNULL ((PyObject *)NULL)
139
140
141int getIntegerAttr(PyObject *self, char *name, char *altername=NULL)
142{ 
143  PyObject *temp = PyObject_GetAttrString(self, name);
144
145  if (!temp && altername) {
146      PyErr_Clear();
147      temp = PyObject_GetAttrString(self, altername);
148  }
149  if (!temp)
150    throw CornException("no attribute '%s'", name);
151  if (!PyInt_Check(temp)) {
152    Py_DECREF(temp);
153    throw CornException("error in attribute '%s': integer expected", name);
154  }
155 
156  int res = (int)PyInt_AsLong(temp);
157  Py_DECREF(temp);
158  return res;
159}
160
161float getFloatAttr(PyObject *self, char *name, char *altername=NULL)
162{ 
163  PyObject *temp = PyObject_GetAttrString(self, name);
164
165  if (!temp && altername) {
166      PyErr_Clear();
167      temp = PyObject_GetAttrString(self, altername);
168  }
169  if (!temp)
170    throw CornException("no attribute '%s'", name);
171  if (!PyFloat_Check(temp)) {
172    Py_DECREF(temp);
173    throw CornException("error in attribute '%s': float expected", name);
174  }
175
176  float res = (float)PyFloat_AsDouble(temp);
177  Py_DECREF(temp);
178  return res;
179}
180
181
182
183class TestedExample {
184public:
185  int actualClass;
186  int iterationNumber;
187  vector<int> classes;
188  vector<vector<float> > probabilities;
189  float weight;
190
191  TestedExample(const int &ac, const int &it, const vector<int> &c, const vector<vector<float> > &p, const float &w = 1.0);
192  TestedExample(PyObject *);
193};
194
195
196class ExperimentResults {
197public:
198  int numberOfIterations, numberOfLearners, numberOfClasses;
199  vector<TestedExample> results;
200  bool weights;
201  int baseClass;
202
203  ExperimentResults(const int &ni, const int &nl, const int &nc, const bool &);
204  ExperimentResults(PyObject *);
205};
206
207
208
209TestedExample::TestedExample(const int &ac, const int &it, const vector<int> &c, const vector<vector<float> > &p, const float &w)
210: actualClass(ac),
211  iterationNumber(it),
212  classes(c),
213  probabilities(p),
214  weight(w)
215{}
216
217
218TestedExample::TestedExample(PyObject *obj)
219: actualClass(getIntegerAttr(obj, "actualClass", "actual_class")),
220  iterationNumber(getIntegerAttr(obj, "iteration_number", "iterationNumber")),
221  weight(getFloatAttr(obj, "weight"))
222
223{ 
224  PyObject *temp = PYNULL;
225
226  try {
227    temp = PyObject_GetAttrString(obj, "classes");
228    if (!temp || !PyList_Check(temp))
229      throw CornException("error in 'classes' attribute");
230
231    int i,e;
232    for(i = 0, e = PyList_Size(temp); i<e; i++) {
233      PyObject *cp = PyList_GetItem(temp, i);
234      if (!cp || !PyInt_Check(cp))
235        throw CornException("error in attribute 'classes'");
236      classes.push_back((int)PyInt_AsLong(cp));
237    }
238    Py_DECREF(temp);
239    temp = PYNULL;
240
241    temp = PyObject_GetAttrString(obj, "probabilities");
242    if (!temp || !PyList_Check(temp))
243      throw CornException("error in attribute 'probabilities'");
244 
245    for(i = 0, e = PyList_Size(temp); i<e; i++) {
246      PyObject *slist = PyList_GetItem(temp, i);
247      if (!slist || !PyList_Check(slist))
248        throw CornException("error in 'probabilities' attribute");
249     
250      probabilities.push_back(vector<float>());
251      for(Py_ssize_t ii = 0, ee = PyList_Size(slist); ii<ee; ii++) {
252        PyObject *fe = PyList_GetItem(slist, ii);
253        if (!fe || !PyFloat_Check(fe))
254          throw CornException("error in 'probabilities' attribute");
255        probabilities.back().push_back((float)PyFloat_AsDouble(fe));
256      }
257    }
258
259    Py_DECREF(temp);
260    temp = PYNULL;
261  }
262  catch (...) {
263    Py_XDECREF(temp);
264  }
265}
266       
267     
268
269ExperimentResults::ExperimentResults(const int &ni, const int &nl, const int &nc, const bool &w)
270: numberOfIterations(ni),
271  numberOfLearners(nl),
272  numberOfClasses(nc),
273  weights(w)
274{}
275
276
277ExperimentResults::ExperimentResults(PyObject *obj)
278: numberOfIterations(getIntegerAttr(obj, "numberOfIterations", "number_of_iterations")),
279  numberOfLearners(getIntegerAttr(obj, "numberOfLearners", "number_of_learners"))
280{ 
281  PyObject *temp = PyObject_GetAttrString(obj, "weights");
282  weights = temp && (PyObject_IsTrue(temp)!=0);
283  Py_XDECREF(temp);
284
285  temp = PyObject_GetAttrString(obj, "baseClass");
286  PyErr_Clear();
287  if (!temp)
288      temp = PyObject_GetAttrString(obj, "base_class");
289  baseClass = temp ? PyInt_AsLong(temp) : -1;
290  Py_XDECREF(temp);
291
292  temp = PyObject_GetAttrString(obj, "classValues");
293  PyErr_Clear();
294  if (!temp)
295      temp = PyObject_GetAttrString(obj, "class_values");
296  if (!temp)
297    throw CornException("no 'classValues' attribute");
298  numberOfClasses = PySequence_Size(temp);
299  Py_DECREF(temp);
300  if (numberOfClasses == -1)
301    throw CornException("'classValues' should contain a list of class names");
302
303  PyObject *pyresults = PyObject_GetAttrString(obj, "results");
304  if (!pyresults)
305    throw CornException("no 'results' attribute");
306
307  if (!PyList_Check(pyresults)) {
308    Py_DECREF(pyresults);
309    throw CornException("'results' is no a list");
310  }
311
312  for(Py_ssize_t i = 0, e = PyList_Size(pyresults); i<e; i++) {
313    PyObject *testedExample = PyList_GetItem(pyresults, i);
314    results.push_back(TestedExample(testedExample));
315  }
316
317  Py_DECREF(pyresults);
318}
319
320
321
322inline float diff2(const float &abnormal, const float &normal)
323{ if (normal<abnormal)
324    return 1;
325  if (normal>abnormal)
326    return 0;
327  return 0.5;
328}
329
330
331
332
333class pp {
334public:
335  float normal, abnormal;
336
337  inline pp()
338  : normal(0.0),
339    abnormal(0.0)
340  {}
341
342  inline void add(const bool &b, const float &w = 1.0)
343  { *(b ? &abnormal : &normal) += w; }
344
345  pp &operator +=(const pp &other)
346  { normal += other.normal;
347    abnormal += other.abnormal;
348    return *this;
349  }
350
351  pp &operator -=(const pp &other)
352  { normal -= other.normal;
353    abnormal -= other.abnormal;
354    return *this;
355  }
356};
357
358typedef map<float, pp> TCummulativeROC;
359
360void C_computeROCCumulative(const ExperimentResults &results, int classIndex, pp &totals, vector<TCummulativeROC> &cummlists, bool useWeights)
361{
362  if (classIndex<0)
363    classIndex = results.baseClass;
364  if (classIndex<0)
365    classIndex = 1;
366  if (classIndex >= results.numberOfClasses)
367    throw CornException("classIndex out of range");
368
369  totals = pp();
370  cummlists = vector<TCummulativeROC>(results.numberOfLearners);
371
372  const_ITERATE(vector<TestedExample>, i, results.results) {
373    bool ind = (*i).actualClass==classIndex;
374    float weight = useWeights ? (*i).weight : 1.0;
375    totals.add(ind, weight);
376
377    vector<TCummulativeROC>::iterator ci(cummlists.begin());
378    const_ITERATE(vector<vector<float> >, pi, (*i).probabilities) {
379      const float &tp = (*pi)[classIndex];
380      (*ci)[tp];
381      (*ci)[tp].add(ind, weight);
382      ci++;
383    }
384  }
385}
386
387
388void C_computeROCCumulativePair(const ExperimentResults &results, int classIndex1, int classIndex2, pp &totals, vector<TCummulativeROC> &cummlists, bool useWeights)
389{
390  if ((classIndex1 >= results.numberOfClasses) || (classIndex2 >= results.numberOfClasses))
391    throw CornException("classIndex out of range");
392
393  totals = pp();
394  cummlists = vector<TCummulativeROC>(results.numberOfLearners);
395
396  const_ITERATE(vector<TestedExample>, i, results.results) {
397    bool ind = (*i).actualClass==classIndex1;
398    if (!ind && ((*i).actualClass!=classIndex2))
399      continue;
400
401    float weight = useWeights ? (*i).weight : 1.0;
402    totals.add(ind, weight);
403
404    vector<TCummulativeROC>::iterator ci(cummlists.begin());
405    const_ITERATE(vector<vector<float> >, pi, (*i).probabilities) {
406      const float &c1 = (*pi)[classIndex1];
407      const float c_sum = c1 + (*pi)[classIndex2];
408      const float tp = (c_sum > 1e-10) ? c1 / c_sum : 0.5;
409      (*ci)[tp];
410      (*ci)[tp].add(ind, weight);
411      ci++;
412    }
413  }
414}
415
416
417class TCDT {
418public:
419  float C, D, T;
420  TCDT()
421  : C(0.0),
422    D(0.0),
423    T(0.0)
424  {}
425};
426
427void C_computeCDT(const vector<TCummulativeROC> &cummlists, vector<TCDT> &cdts)
428{
429  cdts = vector<TCDT>(cummlists.size());
430 
431  vector<TCDT>::iterator cdti (cdts.begin());
432  for (vector<TCummulativeROC>::const_iterator ci(cummlists.begin()), ce(cummlists.end()); ci!=ce; ci++, cdti++) {
433    pp low;
434    for (map<float, pp>::const_iterator cri((*ci).begin()), cre((*ci).end()); cri!=cre; cri++) {
435      const pp &thi = (*cri).second;
436      (*cdti).C += low.normal   * thi.abnormal;
437      (*cdti).D += low.abnormal * thi.normal;
438      (*cdti).T += thi.normal   * thi.abnormal;
439      low += thi;
440    }
441  }
442}
443
444
445PyObject *py_ROCCumulativeList(vector<TCummulativeROC> &cummlists, pp &totals)
446{
447  PyObject *pyresults = PyList_New(cummlists.size());
448  int lrn = 0;
449  ITERATE(vector<TCummulativeROC>, ci, cummlists) {
450    PyObject *pclist = PyList_New((*ci).size());
451    int prb = 0;
452    ITERATE(TCummulativeROC, si, *ci)
453      PyList_SetItem(pclist, prb++, Py_BuildValue("f(ff)", (*si).first, (*si).second.normal, (*si).second.abnormal));
454    PyList_SetItem(pyresults, lrn++, pclist);
455  }
456
457  return Py_BuildValue("N(ff)", pyresults, totals.normal, totals.abnormal);
458}
459
460
461PyObject *py_computeROCCumulative(PyObject *, PyObject *arg)
462{ 
463  PyTRY
464    PyObject *pyresults;
465    int classIndex = -1;
466    PyObject *pyuseweights = NULL;
467    if (!PyArg_ParseTuple(arg, "O|iO", &pyresults, &classIndex, &pyuseweights))
468      PYERROR(PyExc_TypeError, "computeROCCummulative: results and optionally the classIndex and 'useWeights' flag expected", PYNULL);
469
470    bool useweights = pyuseweights && PyObject_IsTrue(pyuseweights)!=0;
471
472    ExperimentResults results(pyresults);
473    if (results.numberOfIterations>1)
474      PYERROR(PyExc_SystemError, "computeCDT: cannot compute CDT for experiments with multiple iterations", PYNULL);
475
476
477    pp totals;
478    vector<TCummulativeROC> cummlists;
479    C_computeROCCumulative(results, classIndex, totals, cummlists, useweights);
480    return py_ROCCumulativeList(cummlists, totals);
481  PyCATCH
482}
483
484
485PyObject *py_computeROCCumulativePair(PyObject *, PyObject *arg)
486{ 
487  PyTRY
488    PyObject *pyresults;
489    int classIndex1, classIndex2;
490    PyObject *pyuseweights = NULL;
491    if (!PyArg_ParseTuple(arg, "Oii|O", &pyresults, &classIndex1, &classIndex2, &pyuseweights))
492      PYERROR(PyExc_TypeError, "computeROCCummulative: results and classIndices, and optional 'useWeights' flag expected", PYNULL);
493
494    bool useweights = pyuseweights && PyObject_IsTrue(pyuseweights)!=0;
495
496    ExperimentResults results(pyresults);
497    if (results.numberOfIterations>1)
498      PYERROR(PyExc_SystemError, "computeCDT: cannot compute CDT for experiments with multiple iterations", PYNULL);
499
500
501    pp totals;
502    vector<TCummulativeROC> cummlists;
503    C_computeROCCumulativePair(results, classIndex1, classIndex2, totals, cummlists, useweights);
504    return py_ROCCumulativeList(cummlists, totals);
505  PyCATCH
506}
507
508
509PyObject *computeCDTList(vector<TCummulativeROC> &cummlists)
510{
511  PyObject *res = NULL, *orngStatModule = NULL;
512
513  try {
514    vector<TCDT> cdts;
515    C_computeCDT(cummlists, cdts);
516
517    PyObject *orngStatModule = PyImport_ImportModule("orngStat");
518    if (!orngStatModule)
519      return PYNULL;
520
521    // PyModule_GetDict and PyDict_GetItemString return borrowed references
522    PyObject *orngStatModuleDict = PyModule_GetDict(orngStatModule);
523    Py_DECREF(orngStatModule);
524    orngStatModule = NULL;
525
526    PyObject *CDTType = PyDict_GetItemString(orngStatModuleDict, "CDT");
527
528    if (!CDTType)
529      PYERROR(PyExc_AttributeError, "orngStat does not define CDT class", PYNULL);
530
531    PyObject *res = PyList_New(cdts.size());
532    int i = 0;
533    ITERATE(vector<TCDT>, cdti, cdts) {
534      PyObject *inargs = Py_BuildValue("fff", (*cdti).C, (*cdti).D, (*cdti).T);
535      PyObject *indict = PyDict_New();
536      PyObject *PyCDT = PyInstance_New(CDTType, inargs, indict);
537      Py_DECREF(inargs);
538      Py_DECREF(indict);
539
540      if (!PyCDT) {
541        Py_XDECREF(res);
542        return PYNULL;
543      }
544      PyList_SetItem(res, i++, PyCDT);
545    }
546
547    return res;
548  }
549  catch (...) {
550    Py_XDECREF(res);
551    Py_XDECREF(orngStatModule);
552    throw;
553  }
554}
555
556
557PyObject *py_computeCDT(PyObject *, PyObject *arg)
558{
559  PyTRY
560    PyObject *pyresults;
561    int classIndex = -1;
562    PyObject *pyuseweights = PYNULL;
563    if (!PyArg_ParseTuple(arg, "O|iO", &pyresults, &classIndex, &pyuseweights))
564      PYERROR(PyExc_TypeError, "computeROCCummulative: results and optionally the classIndex expected", PYNULL);
565
566    bool useweights = pyuseweights && PyObject_IsTrue(pyuseweights)!=0;
567
568    ExperimentResults results(pyresults);
569
570    pp totals;
571    vector<TCummulativeROC> cummlists;
572    C_computeROCCumulative(results, classIndex, totals, cummlists, useweights);
573
574    return computeCDTList(cummlists);
575  PyCATCH
576}
577
578
579PyObject *py_computeCDTPair(PyObject *, PyObject *arg)
580{
581  PyTRY
582    PyObject *pyresults;
583    int classIndex1, classIndex2;
584    PyObject *pyuseweights = PYNULL;
585    if (!PyArg_ParseTuple(arg, "Oii|O", &pyresults, &classIndex1, &classIndex2, &pyuseweights))
586      PYERROR(PyExc_TypeError, "computeROCCummulative: results, two class indices and optional flag for using weights", PYNULL);
587
588    bool useweights = pyuseweights && PyObject_IsTrue(pyuseweights)!=0;
589
590    ExperimentResults results(pyresults);
591
592    pp totals;
593    vector<TCummulativeROC> cummlists;
594    C_computeROCCumulativePair(results, classIndex1, classIndex2, totals, cummlists, useweights);
595
596    return computeCDTList(cummlists);
597  PyCATCH
598}
599
600
601PyObject *py_compare2ROCs(PyObject *, PyObject *arg)
602{ PyTRY
603    PyObject *pyresults;
604    int roc1, roc2, classIndex = -1;
605    PyObject *pyuseweights;
606    if (!PyArg_ParseTuple(arg, "OiiiO", &pyresults, &roc1, &roc2, &classIndex, &pyuseweights))
607      PYERROR(PyExc_TypeError, "compare2ROCs: results and two integer indices (optionally also classIndex) expected", PYNULL);
608
609    bool useweights = PyObject_IsTrue(pyuseweights)!=0;
610    if (useweights)
611      PYERROR(PyExc_SystemError, "compare2ROCs: cannot use weights (weights not implemented yet)", PYNULL);
612
613    ExperimentResults results(pyresults);
614    if (results.numberOfIterations>1)
615      PYERROR(PyExc_SystemError, "computeCDT: cannot compute CDT for experiments with multiple iterations", PYNULL);
616
617    if (classIndex<0)
618      classIndex = results.baseClass;
619    if (classIndex<0)
620      classIndex = 1;
621
622    float e11[] = {0, 0}, e10[] = {0, 0}, e01[] = {0, 0};
623    float e11r = 0, e10r = 0, e01r = 0;
624    float th[] ={0, 0};
625    int m = 0, n = 0;
626    /* m is number of examples with class == classIndex, n are others
627       X_* represent example with class == classIndex, Y_* are others
628    */
629
630    for (vector<TestedExample>::const_iterator i(results.results.begin()), e(results.results.end()); i!=e; i++)
631      if ((*i).actualClass != classIndex) {
632        n++;
633      }
634      else { // (*i).actualClass == classIndex
635        m++;
636
637        float X_i[] = {(*i).probabilities[roc1][classIndex], (*i).probabilities[roc2][classIndex]};
638
639        for (vector<TestedExample>::const_iterator j = i+1; j!=e; j++)
640          if ((*j).actualClass!=classIndex) {
641
642            float Y_j[] = {(*j).probabilities[roc1][classIndex], (*j).probabilities[roc2][classIndex]};
643            float diffs[] = { diff2(X_i[0], Y_j[0]), diff2(X_i[1], Y_j[1]) };
644
645            th[0] += diffs[0];
646            th[1] += diffs[1];
647
648            e11[0] += sqr(diffs[0]);
649            e11[1] += sqr(diffs[1]);
650            e11r   += diffs[0]*diffs[1];
651
652            for (vector<TestedExample>::const_iterator k = j+1; k!=e; k++)
653              if ((*k).actualClass == classIndex) { // B_XXY
654                float X_k[] = { (*k).probabilities[roc1][classIndex], (*k).probabilities[roc2][classIndex] };
655                float diffsk[] = { diff2(X_k[0], Y_j[0]), diff2(X_k[1], Y_j[1]) };
656                e01[0] += diffs[0]*diffsk[0];
657                e01[1] += diffs[1]*diffsk[1];
658                e01r   += diffs[0]*diffsk[1] + diffs[1]*diffsk[0];
659              }
660              else { // B_XYY
661                float Y_k[] = { (*k).probabilities[roc1][classIndex], (*k).probabilities[roc2][classIndex] };
662                float diffsk[] = { diff2(X_i[0], Y_k[0]), diff2(X_i[1], Y_k[1]) };
663                e10[0] += diffs[0]*diffsk[0];
664                e10[1] += diffs[1]*diffsk[1];
665                e10r   += diffs[0]*diffsk[1] + diffs[1]*diffsk[0];
666              }
667          }
668      }
669
670    float n11 = float(m)*float(n), n01 = float(m)*float(n)*float(m-1)/2.0, n10 = float(m)*float(n)*float(n-1)/2.0;
671 
672    th[0] /= n11;
673    th[1] /= n11;
674   
675    e11[0] = e11[0]/n11 - sqr(th[0]);
676    e11[1] = e11[1]/n11 - sqr(th[1]);
677    e11r   = e11r  /n11 - th[0]*th[1];
678
679    e10[0] = e10[0]/n10 - sqr(th[0]);
680    e10[1] = e10[1]/n10 - sqr(th[1]);
681    e10r   = e10r  /n10 - th[0]*th[1];
682
683    e01[0] = e01[0]/n01 - sqr(th[0]);
684    e01[1] = e01[1]/n01 - sqr(th[1]);
685    e01r   = e01r  /n01 - th[0]*th[1];
686
687    float var[] = { ((n-1)*e10[0] + (m-1)*e01[0] + e11[0])/n11, ((n-1)*e10[1] + (m-1)*e01[1] + e11[1])/n11};
688    float SE[]  = { sqrt(var[0]), sqrt(var[1]) };
689
690    float covar = ((n-1)*e10r + (m-1)*e01r + e11r) / n11;
691    float SEr   = sqrt(var[0]+var[1]-2*covar*SE[0]*SE[1]);
692
693    return Py_BuildValue("(ff)(ff)(ff)", th[0], SE[0], th[1], SE[1], th[0]-th[1], SEr);
694  PyCATCH
695}
696
697
698inline float cmphalf(const float &x, const float &y)
699{
700  if (x<y)   return 0;
701  if (x==y)  return 0.25;
702  return 0.5;
703}
704
705PyObject *py_mAUC(PyObject *, PyObject *arg)
706{ PyTRY
707    PyObject *pyresults;
708    PyObject *pyuseweights = PYNULL;
709    if (!PyArg_ParseTuple(arg, "O|O:corn.mAUC", &pyresults, &pyuseweights))
710      PYERROR(PyExc_TypeError, "mAUC: results and optionally use weights flag expected", PYNULL);
711
712    bool useweights = pyuseweights && (PyObject_IsTrue(pyuseweights)!=0);
713    if (useweights)
714      PYERROR(PyExc_SystemError, "mAUC: cannot use weights (weights not implemented yet)", PYNULL);
715
716    ExperimentResults results(pyresults);
717/*    if (results.numberOfIterations>1)
718      PYERROR(PyExc_SystemError, "mAUC: cannot compute CDT for experiments with multiple iterations", PYNULL);
719*/
720
721    const int nLearners = results.numberOfLearners;
722    vector<float> correctPairs(nLearners, 0.0);
723    int usefulPairs = 0;
724
725    for (vector<TestedExample>::const_iterator i1(results.results.begin()), e(results.results.end()); i1!=e; i1++)
726      for (vector<TestedExample>::const_iterator i2(i1); i2!=e; i2++) {
727        const int cls1 = (*i1).actualClass;
728        const int cls2 = (*i2).actualClass;
729        if (cls1 != cls2) {
730          usefulPairs++;
731          vector<float>::iterator cpi(correctPairs.begin());
732          vector<vector<float> >::const_iterator ep1i((*i1).probabilities.begin()), ep2i((*i2).probabilities.begin());
733          for(int cfr = nLearners; cfr--; cpi++, ep1i++, ep2i++)
734            *cpi +=  cmphalf((*ep1i)[cls1], (*ep2i)[cls1])
735                   + cmphalf((*ep2i)[cls2], (*ep1i)[cls2]);
736        }
737      }
738
739    PyObject *res = PyList_New(nLearners);
740    for(int cfr = 0; cfr<nLearners; cfr++)
741      PyList_SetItem(res, cfr, PyFloat_FromDouble(correctPairs[cfr]/usefulPairs));
742    return res;
743  PyCATCH
744}
745
746/* *********** AUXILIARY ROUTINES *************/
747
748class CompCallbackLess {
749public:
750  PyObject *py_compare;
751
752  CompCallbackLess(PyObject *apyc)
753    : py_compare(apyc)
754    { Py_XINCREF(apyc); }
755
756  ~CompCallbackLess()
757    { Py_XDECREF(py_compare); }
758
759  int operator()(PyObject *obj1, PyObject *obj2)
760    { PyObject *args = Py_BuildValue("OO", obj1, obj2);
761      PyObject *result = PyEval_CallObject(py_compare, args);
762      Py_DECREF(args);
763
764      if (!result) 
765        throw pyexception();
766
767      bool res = PyInt_AsLong(result)<0;
768      Py_DECREF(result);
769      return res;
770    }
771};
772
773
774class CompCallbackEqual {
775public:
776  PyObject *py_compare;
777
778  CompCallbackEqual(PyObject *apyc)
779    : py_compare(apyc)
780    { Py_XINCREF(apyc); }
781
782  ~CompCallbackEqual()
783    { Py_XDECREF(py_compare); }
784
785  int operator()(PyObject *obj1, PyObject *obj2)
786    { PyObject *args = Py_BuildValue("OO", obj1, obj2);
787      PyObject *result = PyEval_CallObject(py_compare, args);
788      Py_DECREF(args);
789
790      if (!result) 
791        throw pyexception();
792
793      bool res = (PyInt_AsLong(result)==0);
794      Py_DECREF(result);
795      return res;
796    }
797};
798
799   
800
801/* *********** EXPORT DECLARATIONS ************/
802
803#define DECLARE(name) \
804 {#name, (binaryfunc)py_##name, METH_VARARGS},
805
806PyMethodDef corn_functions[] = {
807     DECLARE(compare2ROCs)
808     DECLARE(computeROCCumulative)
809     DECLARE(computeROCCumulativePair)
810     DECLARE(computeCDT)
811     DECLARE(computeCDTPair)
812     DECLARE(mAUC)
813
814
815     {NULL, NULL}
816};
817
818#undef DECLARE
819
820#undef PyTRY
821#undef PyCATCH
822#undef PYNULL
Note: See TracBrowser for help on using the repository browser.