source: orange/Orange/OrangeWidgets/Data/OWDataSampler.py @ 11748:467f952c108d

Revision 11748:467f952c108d, 15.7 KB checked in by blaz <blaz.zupan@…>, 6 months ago (diff)

Changes in headers, widget descriptions text.

Line 
1import random
2import Orange
3import OWGUI
4from OWWidget import *
5
6NAME = "Data Sampler"
7DESCRIPTION = "Samples data from a data set."
8ICON = "icons/DataSampler.svg"
9PRIORITY = 1125
10CATEGORY = "Data"
11MAINTAINER = "Aleksander Sadikov"
12MAINTAINER_EMAIL = "aleksander.sadikov(@at@)fri.uni-lj.si"
13INPUTS = [("Data", Orange.data.Table, "setData", Default)]
14OUTPUTS = [("Data Sample", Orange.data.Table, ),
15           ("Remaining Data", Orange.data.Table, )]
16
17
18class OWDataSampler(OWWidget):
19    settingsList=["Stratified", "Repeat", "UseSpecificSeed", "RandomSeed",
20    "GroupSeed", "outFold", "Folds", "SelectType", "useCases", "nCases",
21    "selPercentage", "LOO",
22    "CVFolds", "CVFoldsInternal", "nGroups", "pGroups", "GroupText"]
23   
24    contextHandlers = {"":DomainContextHandler("", ["nCases","selPercentage"])}
25    def __init__(self, parent=None, signalManager=None):
26        OWWidget.__init__(self, parent, signalManager, 'SampleData', wantMainArea = 0)
27
28        self.inputs = [("Data", ExampleTable, self.setData)]
29        self.outputs = [("Data Sample", ExampleTable), ("Remaining Data", ExampleTable)]
30
31        # initialization of variables
32        self.data = None                        # dataset (incoming stream)
33        self.indices = None                     # indices that control sampling
34        self.ind = None                         # indices that control sampling
35
36        self.Stratified = 1                     # use stratified sampling if possible?
37        self.Repeat = 0                         # can elements repeat in a sample?
38        self.UseSpecificSeed = 0                # use a specific random seed?
39        self.RandomSeed = 1                     # specific seed used
40        self.GroupSeed = 1                      # current seed for multiple group selection
41        self.outFold = 1                        # folder/group to output
42        self.Folds = 1                          # total number of folds/groups
43
44        self.SelectType = 0                     # sampling type (LOO, CV, ...)
45        self.useCases = 0                       # use a specific number of cases?
46        self.nCases = 25                        # number of cases to use
47        self.selPercentage = 30                 # sample size in %
48        self.LOO = 1                            # use LOO?
49        self.CVFolds = 10                       # number of CV folds
50        self.CVFoldsInternal = 10               # number of CV folds (for internal use)
51        self.nGroups = 3                        # number of groups
52        self.pGroups = [0.1,0.25,0.5]           # sizes of groups
53        self.GroupText = '0.1,0.25,0.5'         # assigned to Groups Control (for internal use)
54
55        self.loadSettings()
56        # GUI
57       
58        # Info Box
59        box1 = OWGUI.widgetBox(self.controlArea, "Information", addSpace=True)
60        self.infoa = OWGUI.widgetLabel(box1, 'No data on input.')
61        self.infob = OWGUI.widgetLabel(box1, ' ')
62        self.infoc = OWGUI.widgetLabel(box1, ' ')
63       
64        # Options Box
65        box2 = OWGUI.widgetBox(self.controlArea, 'Options', addSpace=True)
66        OWGUI.checkBox(box2, self, 'Stratified', 'Stratified (if possible)', callback=self.settingsChanged)
67        OWGUI.checkWithSpin(box2, self, 'Set random seed:', 0, 32767, 'UseSpecificSeed', 'RandomSeed', checkCallback=self.settingsChanged, spinCallback=self.settingsChanged)
68
69        # Sampling Type Box
70        self.s = [None, None, None, None]
71        self.sBox = OWGUI.widgetBox(self.controlArea, "Sampling type", addSpace=True)
72        self.sBox.buttons = []
73
74        # Random Sampling
75        self.s[0] = OWGUI.appendRadioButton(self.sBox, self, "SelectType", 'Random sampling')
76       
77        # indent
78        indent = sep=OWGUI.checkButtonOffsetHint(self.s[0])
79        # repeat checkbox
80        self.h1Box = OWGUI.indentedBox(self.sBox, sep=indent, orientation = "horizontal")
81        OWGUI.checkBox(self.h1Box, self, 'Repeat', 'With replacement', callback=self.settingsChanged)
82
83        # specified number of elements checkbox
84        self.h2Box = OWGUI.indentedBox(self.sBox, sep=indent, orientation = "horizontal")
85        OWGUI.checkWithSpin(self.h2Box, self, 'Sample size (instances):', 1, 1000000000, 'useCases', 'nCases', checkCallback=[self.uCases, self.settingsChanged], spinCallback=self.settingsChanged)
86        OWGUI.rubber(self.h2Box)
87       
88        # percentage slider
89        self.h3Box = OWGUI.indentedBox(self.sBox, sep=indent, orientation = "horizontal")
90        OWGUI.widgetLabel(self.h3Box, "Sample size:")
91        self.slidebox = OWGUI.indentedBox(self.sBox, sep=indent, orientation = "horizontal")
92        OWGUI.hSlider(self.slidebox, self, 'selPercentage', minValue=1, maxValue=100, step=1, ticks=10, labelFormat="   %d%%", callback=self.settingsChanged)
93
94        # Cross Validation
95        self.s[1] = OWGUI.appendRadioButton(self.sBox, self, "SelectType", 'Cross validation')
96       
97        box = OWGUI.indentedBox(self.sBox, sep=indent, orientation = "horizontal")
98        OWGUI.spin(box, self, 'CVFolds', 2, 100, step=1, label='Number of folds:  ', callback=[self.changeCombo, self.settingsChanged])
99        OWGUI.rubber(box)
100
101        # Leave-One-Out
102        self.s[2] = OWGUI.appendRadioButton(self.sBox, self, "SelectType", 'Leave-one-out')
103
104        # Multiple Groups
105        self.s[3] = OWGUI.appendRadioButton(self.sBox, self, "SelectType", 'Multiple subsets')
106        gbox = OWGUI.indentedBox(self.sBox, sep=indent, orientation = "horizontal")
107        OWGUI.lineEdit(gbox, self, 'GroupText', label='Subset sizes (e.g. "0.1, 0.2, 0.5"):', callback=self.multipleChanged)
108
109        # Output Group Box
110        self.foldcombo = OWGUI.comboBox(self.controlArea, self, "outFold", 'Output Data for Fold / Group', 'Fold / group:', orientation = "horizontal", items = range(1,101), callback = self.foldChanged, sendSelectedValue = 1, valueType = int)
111        self.foldcombo.setEnabled(False)
112
113        # Select Data Button
114        OWGUI.rubber(self.controlArea)
115        self.sampleButton = OWGUI.button(self.controlArea, self, 'Sample &Data', callback = self.process, addToLayout=False, default=True)
116        self.buttonBackground.layout().setDirection(QBoxLayout.TopToBottom)
117        self.buttonBackground.layout().insertWidget(0, self.sampleButton)
118        self.buttonBackground.show()
119        self.s[self.SelectType].setChecked(True)    # set initial radio button on (default sample type)
120
121        # CONNECTIONS
122        # set connections for RadioButton (SelectType)
123        self.dummy1 = [None]*len(self.s)
124        for i in range(len(self.s)):
125            self.dummy1[i] = lambda x, v=i: self.sChanged(x, v)
126            self.connect(self.s[i], SIGNAL("toggled(bool)"), self.dummy1[i])
127
128        # final touch
129        self.resize(200, 275)
130
131    # CONNECTION TRIGGER AND GUI ROUTINES
132    # enables RadioButton switching
133    def sChanged(self, value, id):
134        self.SelectType = id
135        self.process()
136
137    def multipleChanged(self):
138        try:
139            self.pGroups = [float(x) for x in self.GroupText.split(',')]
140            self.nGroups = len(self.pGroups)
141            self.error(1)
142        except:
143            self.error("Invalid specification for sizes of subsets.", 1)
144        self.changeCombo()
145        self.settingsChanged()
146
147    # reflect user's actions that change combobox contents
148    def changeCombo(self):
149        # refill combobox
150        self.Folds = 1
151        if self.SelectType == 1:
152            self.Folds = self.CVFolds
153        elif self.SelectType == 2:
154            if self.data:
155                self.Folds = len(self.data)
156            else:
157                self.Folds = 1
158        elif self.SelectType == 3:
159            self.Folds = self.nGroups
160        self.foldcombo.clear()
161        for x in range(self.Folds):
162            self.foldcombo.addItem(str(x+1))
163
164    # triggered on change in output fold combobox
165    def foldChanged(self):
166        #self.outFold = int(ix+1)
167        if self.data:
168            self.sdata()
169
170    # switches between cases and percentage (random sampling)
171    def uCases(self):
172        if self.useCases == 1:
173            self.h3Box.setEnabled(False)
174            self.slidebox.setEnabled(False)
175        else:
176            self.h3Box.setEnabled(True)
177            self.slidebox.setEnabled(True)
178
179    # I/O STREAM ROUTINES
180    # handles changes of input stream
181    def setData(self, dataset):
182        self.closeContext()
183        if dataset:
184            self.infoa.setText('%d instances in input data set.' % len(dataset))
185            self.data = dataset
186            self.openContext("", dataset)
187            self.process()
188        else:
189            self.infoa.setText('No data on input.')
190            self.infob.setText('')
191            self.infoc.setText('')
192            self.send("Data Sample", None)
193            self.send("Remaining Data", None)
194            self.data = None
195
196    # feeds the output stream
197    def sdata(self):
198        # select data
199        if self.SelectType == 0:
200            if self.useCases == 1 and self.Repeat == 1:
201                sample = orange.ExampleTable(self.data.domain)
202                for x in range(self.nCases):
203                    sample.append(self.data[random.randint(0,len(self.data)-1)])
204                remainder = None
205                self.infob.setText('Random sampling with repetitions, %d instances.' % self.nCases)
206            else:
207                sample = self.data.select(self.ind, 0)
208                remainder = self.data.select(self.ind, 1)
209            self.infoc.setText('Output: %d instances.' % len(sample))
210        elif self.SelectType == 3:
211            self.ind = self.indices(self.data, p0 = self.pGroups[self.outFold-1])
212            sample = self.data.select(self.ind, 0)
213            remainder = self.data.select(self.ind, 1)
214            self.infoc.setText('Output: subset %(outFold)d of %(folds)d, %(instances)d instance(s).' % {"outFold": self.outFold, "folds": self.Folds, "instances": len(sample)})
215        else:
216            sample = self.data.select(self.ind, self.outFold-1)
217            remainder = self.data.select(self.ind, self.outFold-1, negate=1)
218            self.infoc.setText('Output: fold %(outFold)d of %(folds)d, %(instances)d instance(s).' % {"outFold": self.outFold, "folds": self.Folds, "instances": len(sample)})
219        # set name (by PJ)
220        if sample:
221            sample.name = self.data.name
222        if remainder:
223            remainder.name = self.data.name
224        # send data
225        self.nSample = len(sample)
226        self.nRemainder = len(remainder) if remainder is not None else 0
227        self.send("Data Sample", sample)
228        self.send("Remaining Data", remainder)
229       
230        self.sampleButton.setEnabled(False)
231
232    # MAIN SWITCH
233    # processes data after the user requests it
234    def process(self):
235        # reset errors, fold selected
236        self.error(0)
237        self.outFold = 1
238
239        # check for data
240        if self.data == None:
241            return
242        else:
243            self.infob.setText('')
244            self.infoc.setText('')
245
246        # Random Selection
247        if self.SelectType == 0:
248            # apply selected options
249            if self.useCases == 1 and self.Repeat != 1:
250                if self.nCases > len(self.data):
251                    self.error(0, "Sample size (w/o repetitions) larger than dataset.")
252                    return
253                self.indices = orange.MakeRandomIndices2(p0=int(self.nCases))
254                self.infob.setText('Random sampling, using exactly %d instances.' % self.nCases)
255            else:
256                if self.selPercentage == 100:
257                    self.indices = orange.MakeRandomIndices2(p0=int(len(self.data)))
258                else:
259                    self.indices = orange.MakeRandomIndices2(p0=float(self.selPercentage/100.0))
260                self.infob.setText('Random sampling, %d%% of input instances.' % self.selPercentage)
261            if self.Stratified == 1: self.indices.stratified = self.indices.StratifiedIfPossible
262            else:                    self.indices.stratified = self.indices.NotStratified
263            if self.UseSpecificSeed == 1: self.indices.randseed = self.RandomSeed
264            else:                         self.indices.randomGenerator = orange.RandomGenerator(random.randint(0,65536))
265
266            # call output stream handler to send data
267            self.ind = self.indices(self.data)
268
269        # Cross Validation / LOO
270        elif self.SelectType == 1 or self.SelectType == 2:
271            # apply selected options
272            if self.SelectType == 2:
273                self.CVFoldsInternal = len(self.data)
274                self.infob.setText('Leave-one-out.')
275            else:
276                self.CVFoldsInternal = self.CVFolds
277                self.infob.setText('%d-fold cross validation.' % self.CVFolds)
278            self.indices = orange.MakeRandomIndicesCV(folds = self.CVFoldsInternal)
279            if self.Stratified == 1:
280                self.indices.stratified = self.indices.StratifiedIfPossible
281            else:
282                self.indices.stratified = self.indices.NotStratified
283            if self.UseSpecificSeed == 1:
284                #self.indices.randomGenerator = orange.RandomGenerator(random.randint(0,65536))
285                self.indices.randseed = self.RandomSeed
286            else:
287                #self.indices.randomGenerator = orange.RandomGenerator(random.randint(0,65536))
288                self.indices.randseed = random.randint(0,65536)
289
290            # call output stream handler to send data
291            self.ind = self.indices(self.data)
292
293        # MultiGroup
294        elif self.SelectType == 3:
295            self.infob.setText('Multiple subsets.')
296            #parse group specification string
297
298            #prepare indices generator
299            self.indices = orange.MakeRandomIndices2()
300            if self.Stratified == 1: self.indices.stratified = self.indices.StratifiedIfPossible
301            else:                    self.indices.stratified = self.indices.NotStratified
302            if self.UseSpecificSeed == 1: self.indices.randseed = self.RandomSeed
303            else:                         self.indices.randomGenerator = orange.RandomGenerator(random.randint(0,65536))
304
305        # enable fold selection and fill combobox if applicable
306        if self.SelectType == 0:
307            self.foldcombo.setEnabled(False)
308        else:
309            self.foldcombo.setEnabled(True)
310            self.changeCombo()
311
312        # call data output routine
313        self.sdata()
314       
315    def settingsChanged(self):
316        self.sampleButton.setEnabled(True)
317
318    def sendReport(self):
319        if self.SelectType == 0:
320            if self.useCases:
321                stype = "Random sample of %i instances" % self.nCases
322            else:
323                stype = "Random sample with %i%% instances" % self.selPercentage
324        elif self.SelectType == 1:
325            stype = "%i-fold cross validation" % self.CVFolds
326        elif self.SelectType == 2:
327            stype = "Leave one out"
328        elif self.SelectType == 3:
329            stype = "Multiple subsets"
330        self.reportSettings("Settings", [("Sampling type", stype), 
331                                         ("Stratification", OWGUI.YesNo[self.Stratified]),
332                                         ("Random seed", str(self.RandomSeed) if self.UseSpecificSeed else "auto")])
333        if self.data is not None:
334            self.reportSettings("Data", [("Input", "%i examples" % len(self.data)), 
335                                         ("Sample", "%i examples" % self.nSample), 
336                                         ("Rest", "%i examples" % self.nRemainder)])
337        else:
338            self.reportSettings("Data", [("Input", "None")])
339
340##############################################################################
341# Test the widget, run from prompt
342
343if __name__=="__main__":
344    appl = QApplication(sys.argv)
345    ow = OWDataSampler()
346    data = Orange.data.Table('iris.tab')
347    ow.setData(data)
348    ow.show()
349    appl.exec_()
350    ow.saveSettings()
Note: See TracBrowser for help on using the repository browser.