#!/usr/bin/pythonw ################################################################################ # # EftAnalyzer2.py - Embedded Figures Test Results Analyzer # Copyright (C) 2009 Alan G. Carter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see http://www.gnu.org/licenses/. # ################################################################################ import wx import os import string from math import sqrt ID_QUIT = 1 ID_OPEN = 2 GRAPH_BEST = 0 GRAPH_MIDDLE = 1 GRAPH_WORST = 2 GRAPH_ALL = 3 GRAPH_DISTRIB_BOTH = 1 GRAPH_DISTRIB_NAUSEOUS = 2 GRAPH_DISTRIB_NON_NAUSEOUS = 3 GRAPH_DISTRIB_STRESSORS = 4 GRAPH_DISTRIB_AGE = 5 GRAPH_DISTRIB_DRUG = 6 GRAPH_STRESSORS_BY_SCORE = 7 GRAPH_STRESSORS_BY_AV_SCORE = 8 GRAPH_STRESSORS_BY_AV_AGE = 9 GRAPH_EXERCISES_BY_AV_SCORE = 10 GRAPH_AGE_BY_AV_SCORE = 11 scalingTable = (( 5, 1), ( 10, 1), ( 20, 2), ( 25, 5), ( 30, 3), ( 50, 5), (100, 10), (200, 20), (250, 25), (500, 50)) pharmaData = (("Caffeine", wx.Colour( 0, 0, 255)), ("Alcohol", wx.Colour( 0, 255, 0)), ("Tobacco", wx.Colour(255, 0, 0)), ("Ritalin", wx.Colour(255, 255, 0)), ("Benzodiazepines", wx.Colour(255, 0, 255)), ("SSRIs", wx.Colour( 0, 255, 255)), ("Marijuana", wx.Colour( 0, 0, 127)), ("MDMA", wx.Colour( 0, 127, 0)), ("Cocaine", wx.Colour(127, 0, 0))) partitions = ((lambda p: 0, lambda p: 0, lambda p: 0 ), (lambda p: p/3, lambda p: (p/3)+1, lambda p: (p/3)+1 ), (lambda p: (p/3)*2, lambda p: ((p/3)*2)+1, lambda p: ((p/3)+1)*2), (lambda p: p, lambda p: p, lambda p: p )) ################################################################################ # # Class Entry # ################################################################################ class Entry: def __init__(self): self.id = "" self.isMale = False self.ageGroup = 0 self.occupation = "" self.isGeek = False self.eveningWalks = False self.culturalActivities = False self.imaginaryFriend = False self.history = False self.cooking = False self.meditation = False self.changeRoutes = False self.changeJob = False self.moveHome = False self.fallInLove = False self.breakUp = False self.newCar = False self.vacation = False self.seeOldFriends = False self.getMoreSleep = False self.disaster = False self.iEnjoyMyJob = 0 self.myJobIsWellDefined = 0 self.myCoworkersAreCooperative = 0 self.myWorkplaceIsStressful = 0 self.myJobIsStressful = 0 self.moraleIsGoodWhereIWork = 0 self.iGetFrustratedAtWork = 0 self.isNauseous = False self.drugs = [False for index in range(len(pharmaData))]; self.otherMeds = "" self.otherDrugs = "" self.before = 0 self.after = 0 ############################################################################ def GetStressScore(self): return (self.iEnjoyMyJob + self.myJobIsWellDefined + self.myCoworkersAreCooperative + self.myWorkplaceIsStressful + self.myJobIsStressful + self.moraleIsGoodWhereIWork + self.iGetFrustratedAtWork) ############################################################################ def GetExerciseScore(self): count = 0; if self.eveningWalks: count = count + 1 if self.culturalActivities: count = count + 1 if self.imaginaryFriend: count = count + 1 if self.history: count = count + 1 if self.cooking: count = count + 1 if self.meditation: count = count + 1 if self.changeRoutes: count = count + 1 if self.changeJob: count = count + 1 if self.moveHome: count = count + 1 if self.fallInLove: count = count + 1 if self.breakUp: count = count + 1 if self.newCar: count = count + 1 if self.vacation: count = count + 1 if self.seeOldFriends: count = count + 1 if self.getMoreSleep: count = count + 1 if self.disaster: count = count + 1 return count ################################################################################ # # Class EftApp # ################################################################################ class EftApp(wx.App): def OnInit(self): frame = StatsFrame() frame.Show() self.SetTopWindow(frame) return True ################################################################################ # # Class StatsFrame # ################################################################################ class StatsFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, "EFT Analyzer", pos=wx.DefaultPosition, size=(600, 450)) self.entries = {} self.valids = [] menuFile = wx.Menu() menuFile.Append(ID_OPEN, "Open...") menuFile.Append(ID_QUIT, "Exit") menuBar = wx.MenuBar() menuBar.Append(menuFile, "File") self.SetMenuBar(menuBar) self.Bind(wx.EVT_MENU, self.OnQuit, id=ID_QUIT) self.Bind(wx.EVT_MENU, self.OnOpen, id=ID_OPEN) self.gridSizer = wx.GridSizer(cols=2, vgap=0, hgap=5) self.totalDisplay = self.AddReport("Total Entries") self.validDisplay = self.AddReport("Valid Entries") self.singlesDisplay = self.AddReport("Single Values") self.pairsDisplay = self.AddReport("Paired Values") self.geeksDisplay = self.AddReport("Geeks") self.nonGeeksDisplay = self.AddReport("Non Geeks") self.malesDisplay = self.AddReport("Male") self.nonMalesDisplay = self.AddReport("Female") self.nauseatorsDisplay = self.AddReport("Nauseators") self.nonNauseatorsDisplay = self.AddReport("Non Nauseators") self.drugDisplays = [self.AddReport(pharmaData[index][0]) for index in range(len(pharmaData))] self.SetSizer(self.gridSizer) self.Fit() self.frames = [] self.Spawn(GRAPH_DISTRIB_BOTH) self.Spawn(GRAPH_DISTRIB_NAUSEOUS) self.Spawn(GRAPH_DISTRIB_NON_NAUSEOUS) self.Spawn(GRAPH_DISTRIB_STRESSORS) self.Spawn(GRAPH_DISTRIB_AGE) self.Spawn(GRAPH_DISTRIB_DRUG) self.Spawn(GRAPH_STRESSORS_BY_SCORE, GRAPH_ALL) self.Spawn(GRAPH_STRESSORS_BY_SCORE, GRAPH_WORST) self.Spawn(GRAPH_STRESSORS_BY_SCORE, GRAPH_BEST) self.Spawn(GRAPH_STRESSORS_BY_SCORE, GRAPH_MIDDLE) self.Spawn(GRAPH_STRESSORS_BY_AV_SCORE, GRAPH_ALL) self.Spawn(GRAPH_STRESSORS_BY_AV_SCORE, GRAPH_WORST) self.Spawn(GRAPH_STRESSORS_BY_AV_SCORE, GRAPH_BEST) self.Spawn(GRAPH_STRESSORS_BY_AV_SCORE, GRAPH_MIDDLE) self.Spawn(GRAPH_STRESSORS_BY_AV_AGE, GRAPH_ALL) self.Spawn(GRAPH_STRESSORS_BY_AV_AGE, GRAPH_WORST) self.Spawn(GRAPH_STRESSORS_BY_AV_AGE, GRAPH_BEST) self.Spawn(GRAPH_STRESSORS_BY_AV_AGE, GRAPH_MIDDLE) self.Spawn(GRAPH_AGE_BY_AV_SCORE, GRAPH_ALL) self.Spawn(GRAPH_AGE_BY_AV_SCORE, GRAPH_WORST) self.Spawn(GRAPH_AGE_BY_AV_SCORE, GRAPH_BEST) self.Spawn(GRAPH_AGE_BY_AV_SCORE, GRAPH_MIDDLE) self.Spawn(GRAPH_EXERCISES_BY_AV_SCORE) ############################################################################ def OnOpen(self, event): # Clear the current data structures. self.Purge() # Pick a file to open. dialog = wx.FileDialog(self, "EFT Results File", os.getcwd(), "", "*.txt", wx.OPEN) if dialog.ShowModal() != wx.ID_OK: return # Open the file. try: textfile = open(dialog.GetPath(), "r") except IOError, e: return # Throw away the first line. if not textfile.readline(): return # Read and strip newlines and parse the remaining lines. for line in textfile.readlines(): self.Parse(filter(lambda x: x != "\n", line)) # Report the total number of entries - an entry == many lines. self.totalDisplay.SetValue(str(len(self.entries))) # Now remove invalid entries. Valid entries have values: # # 100 < Before < 10000 # 0 <= After < 10000 # # After may be 0 to indicate no value. validCount = 0 singlesCount = 0 pairsCount = 0 geeksCount = 0 nonGeeksCount = 0 malesCount = 0 nonMalesCount = 0 nauseatorsCount = 0 nonNauseatorsCount = 0 drugCounts = [0 for index in range(len(pharmaData))] self.valids = filter(lambda e: ((e.before > 100) and (e.before <= 9999) and (e.after >= 0) and (e.after <= 9999)), self.entries.values()) self.validDisplay.SetValue(str(len(self.valids))) for e in self.valids: if e.after == 0: singlesCount += 1 else: pairsCount += 1 if e.isGeek: geeksCount += 1 else: nonGeeksCount += 1 if e.isMale: malesCount += 1 else: nonMalesCount += 1 if e.isNauseous: nauseatorsCount += 1 else: nonNauseatorsCount += 1 for count in range(len(pharmaData)): if e.drugs[count]: drugCounts[count] += 1 # Report the other statistics. self.singlesDisplay.SetValue(str(singlesCount)) self.pairsDisplay.SetValue(str(pairsCount)) self.geeksDisplay.SetValue(str(geeksCount)) self.nonGeeksDisplay.SetValue(str(nonGeeksCount)) self.malesDisplay.SetValue(str(malesCount)) self.nonMalesDisplay.SetValue(str(nonMalesCount)) self.nauseatorsDisplay.SetValue(str(nauseatorsCount)) self.nonNauseatorsDisplay.SetValue(str(nonNauseatorsCount)) for count in range(len(pharmaData)): self.drugDisplays[count].SetValue(str(drugCounts[count])) # With new entries, refresh all existing frames. for f in self.frames: f.Refresh() self.Refresh(); ############################################################################ def OnQuit(self, event): for f in self.frames: f.Close() self.Close() ############################################################################ def AddReport(self, name): self.gridSizer.Add(wx.StaticText(self, wx.ID_ANY, name)) tc = wx.TextCtrl(self, wx.ID_ANY, "0") self.gridSizer.Add(tc) return tc ############################################################################ def Purge(self): self.entries = {} self.valids = [] ############################################################################ def Parse(self, line): tokens = line.split("\t") # The fourth field contains the value. We don't need to do anything if # the value isn't there! if len(tokens) != 4: return # The first field is a unique line ID, which we don't need. The second # is the record ID the line belongs to. See if we have a record in the # dict, and if not create one. if tokens[1] not in self.entries: self.entries[tokens[1]] = Entry() theEntry = self.entries[tokens[1]]; # The third field is the question. The questions are collected into # groups whose values (in the fourth field) are handled in different # ways. if tokens[2] == "Gender": theEntry.isMale = (tokens[3] == "Male") elif tokens[2] == "Age": if tokens[3] == "0 - 9": theEntry.ageGroup = 0 elif tokens[3] == "10 - 19": theEntry.ageGroup = 1 elif tokens[3] == "20 - 29": theEntry.ageGroup = 2 elif tokens[3] == "30 - 39": theEntry.ageGroup = 3 elif tokens[3] == "40 - 49": theEntry.ageGroup = 4 elif tokens[3] == "50 - 59": theEntry.ageGroup = 5 elif tokens[3] == "60 - 69": theEntry.ageGroup = 6 elif tokens[3] == "70 - 79": theEntry.ageGroup = 7 elif tokens[3] == "80 - 89": theEntry.ageGroup = 8 elif tokens[3] == "90 - 99": theEntry.ageGroup = 9 elif tokens[3] == "100+": theEntry.ageGroup = 10 elif tokens[2] == "Occupation": theEntry.occupation = tokens[3] # Normalize the occupations to lower case. occ = tokens[3].lower() # Look for geek implying substrings. Not ideal but a guess. if (("software" in occ) or ("programm" in occ) or ("system" in occ) or ("sysadmin" in occ) or ("sysadm" in occ) or ("devel" in occ) or ("engr" in occ) or ("sd" in occ) or ("engineer" in occ) or ("computer" in occ) or ("mathemat" in occ) or ("comp-sci" in occ) or ("csstudent" in occ) or ("dba" in occ) or ("web" in occ)): theEntry.isGeek = True; elif tokens[2] == "New Activities": act = tokens[3] if "Evening Walks" in act: theEntry.eveningWalks = True; if "Cultural Activities" in act: theEntry.culturalActivities = True; if "Imaginary Friend" in act: theEntry.imaginaryFriend = True; if "History" in act: theEntry.history = True; if "Cooking" in act: theEntry.cooking = True; if "Meditation" in act: theEntry.meditation = True; if "Change Routes" in act: theEntry.changeRoutes = True; if "Change Job" in act: theEntry.changeJob = True; if "Move Home" in act: theEntry.moveHome = True; if "Fall In Love" in act: theEntry.fallInLove = True; if "Break Up" in act: theEntry.breakUp = True; if "New Car" in act: theEntry.newCar = True; if "Vacation" in act: theEntry.vacation = True; if "See Old Friends" in act: theEntry.seeOldFriends = True; if "Get More Sleep" in act: theEntry.getMoreSleep = True; if "Disaster" in act: theEntry.disaster = True; elif tokens[2] == "I Enjoy My Job": theEntry.iEnjoyMyJob = self.encodeChoice(tokens[3]) elif tokens[2] == "My Job Is Well Defined": theEntry.myJobIsWellDefined = self.encodeChoice(tokens[3]) elif tokens[2] == "My Co-workers Are Co-operative": theEntry.myCoworkersAreCooperative = self.encodeChoice(tokens[3]) elif tokens[2] == "My Workplace Is Stressful": theEntry.myWorkplaceIsStressful = self.encodeChoice(tokens[3]) * -1 elif tokens[2] == "My Job Is Stressful": theEntry.myJobIsStressful = self.encodeChoice(tokens[3]) * -1 elif tokens[2] == "Morale Is Good Where I Work": theEntry.moraleIsGoodWhereIWork = self.encodeChoice(tokens[3]) elif tokens[2] == "I Get Frustrated At Work": theEntry.iGetFrustratedAtWork = self.encodeChoice(tokens[3]) * -1 elif tokens[2] == "I Feel Nauseous When Very Bored": theEntry.isNauseous = (self.encodeChoice(tokens[3]) > 0) elif (tokens[2] == "Prescription Meds") or \ (tokens[2] == "Non-Prescription Drugs"): for index, value in enumerate(pharmaData): if value[0] in tokens[3]: theEntry.drugs[index] = True elif tokens[2] == "Other Meds": theEntry.otherMeds = tokens[3]; elif tokens[2] == "Other Drugs": theEntry.otherDrugs = tokens[3] if "caff" in tokens[3].lower(): theEntry.drugs[0] = True; elif tokens[2] == "Before": theEntry.before = self.ValidateInt(tokens[3]) elif tokens[2] == "After": theEntry.after = self.ValidateInt(tokens[3]) ############################################################################ def encodeChoice(self, choice): if choice == "Strongly Agree": return 2 elif choice == "Agree": return 1 elif choice == "Disagree": return -1 elif choice == "Strongly Disagree": return -2 else: return 0 ############################################################################ def ValidateInt(self, text): valid = True if len(text) == 0: return 0 else: for c in text: if c not in string.digits: valid = False if valid: return int(text) else: return -1 ############################################################################ def Spawn(self, graphType, graphBand=0): self.frames.append(GraphFrame(self, graphType, graphBand)) ############################################################################ def DeRegisterFrame(self, frame): self.frames.remove(frame) ################################################################################ # # Class GraphFrame # ################################################################################ class GraphFrame(wx.Frame): def __init__(self, parent, type, band): wx.Frame.__init__(self, parent, wx.ID_ANY, "", pos=wx.DefaultPosition, size=wx.DefaultSize) self.parent = parent topSizer = wx.BoxSizer(wx.HORIZONTAL) if type == GRAPH_DISTRIB_BOTH or \ type == GRAPH_DISTRIB_NAUSEOUS or \ type == GRAPH_DISTRIB_NON_NAUSEOUS: topSizer.Add(DistribPanel(self, parent, type), 1, wx.EXPAND) elif type == GRAPH_DISTRIB_STRESSORS: topSizer.Add(StressorsDistribPanel(self, parent), 1, wx.EXPAND) elif type == GRAPH_DISTRIB_AGE: topSizer.Add(AgeDistribPanel(self, parent), 1, wx.EXPAND) elif type == GRAPH_DISTRIB_DRUG: topSizer.Add(DrugDistribPanel(self, parent), 1, wx.EXPAND) elif type == GRAPH_STRESSORS_BY_SCORE: topSizer.Add(StressorsByScorePanel(self, parent, band), 1, wx.EXPAND) elif type == GRAPH_STRESSORS_BY_AV_SCORE: topSizer.Add(StressorsByAvScorePanel(self, parent, band), 1, wx.EXPAND) elif type == GRAPH_STRESSORS_BY_AV_AGE: topSizer.Add(StressorsByAvAgePanel(self, parent, band), 1, wx.EXPAND) elif type == GRAPH_EXERCISES_BY_AV_SCORE: topSizer.Add(ExercisesByAvScorePanel(self, parent), 1, wx.EXPAND) elif type == GRAPH_AGE_BY_AV_SCORE: topSizer.Add(AgeByAvScorePanel(self, parent, band), 1, wx.EXPAND) self.Bind(wx.EVT_CLOSE, self.OnClose) self.SetSizer(topSizer) self.Fit() self.Show() ############################################################################ def OnClose(self, event): self.parent.DeRegisterFrame(self) self.Destroy() ################################################################################ # # Class GraphPanel # ################################################################################ class GraphPanel(wx.Panel): def __init__(self, parent, statsFrame): wx.Panel.__init__(self, parent, wx.ID_ANY, wx.DefaultPosition, size=(600, 600)) self.statsFrame = statsFrame self.SetBackgroundColour(wx.Colour(255, 255, 255)) self.ClearBackground() self.Bind(wx.EVT_PAINT, self.OnPaint) ############################################################################ def ChooseColoursDrawAxes(self, dc): dc.SetPen(wx.Pen("black")) dc.SetBrush(wx.Brush("grey")) dc.SetTextForeground("black") dc.DrawLine(100, 500, 600, 500) dc.DrawLine(100, 0, 100, 500) ############################################################################ def DrawScaleX(self, dc, gap, start, end, step, origin, legend): for count in range(start, end, step): buffer = str(count) width, height = dc.GetTextExtent(buffer) offset = (gap - width) / 2 dc.DrawText(buffer, 100 + ((count + origin) * gap) + offset, 500 + 20) width, height = dc.GetTextExtent(legend) offset = (500 - width) / 2 dc.DrawText(legend, 100 + offset, 500 + 40) ############################################################################ def DrawScaleY(self, dc, maxScale, division, legend): pixelsPerNumber = 500 / maxScale pixelsPerDivision = pixelsPerNumber * division; for count in range(maxScale / division): buffer = str(count * division) dc.DrawText(buffer, 60, 500 - (count * pixelsPerDivision)) width, height = dc.GetTextExtent(legend) offset = (500 - width) / 2 dc.DrawRotatedText(legend, 30, 500 - offset, 90.0) ############################################################################ def ExtractStressBand(self, stressBand, graphBand): bologians = [] for e in self.statsFrame.valids: if e.GetStressScore() == stressBand: bologians.append(e) bologians.sort(lambda a, b: cmp(a.before, b.before)) if graphBand == GRAPH_ALL: return bologians else: p = len(bologians) return bologians[partitions[graphBand ][p % 3](p) : partitions[graphBand + 1][p % 3](p) ] ############################################################################ def ComputeAndPlotMeansAndSDs(self, dc, workingSet, getBand, getScore, colour, offset, division, scaleY, stagger, radius): totalScores = {} totalCounts = {} means = {} summedSquares = {} # Initialize dictionary entries which will have a non-zero value. for e in workingSet: i = getBand(e) totalScores[i] = 0.0 totalCounts[i] = 0.0 means[i] = 0.0 summedSquares[i] = 0.0 for e in workingSet: totalScores[getBand(e)] += getScore(e) totalCounts[getBand(e)] += 1 for i, v in totalCounts.iteritems(): means[i] = totalScores[i] / v summedSquares[i] = 0 # Now make a second pass to calculate the standard deviation. for e in workingSet: temp = getScore(e) - means[getBand(e)] summedSquares[getBand(e)] += (temp * temp) # Now draw the means and standard deviations. dc.SetPen(wx.Pen(colour)) dc.SetBrush(wx.Brush(colour)) for i, v in totalCounts.iteritems(): x = i y = totalScores[i] / v d = int(sqrt(summedSquares[i] / v)) posX = 100 + offset + (x * division) + (division / 2) + stagger dc.DrawCircle(posX, 500 - int(float(y) / scaleY), radius) dc.DrawLine(posX, 500 - int(float(y + d) / scaleY), posX, 500 - int(float(y - d) / scaleY)) ############################################################################ def DrawHistogram(self, dc, workingSet, maxScale): gapX = 500 / len(workingSet) gapY = 500 / maxScale for count in range(len(workingSet)): dc.DrawRectangle(100 + (count * gapX), 500 - workingSet[count] * gapY, gapX, workingSet[count] * gapY) ############################################################################ def SelectScaleAndDivision(self, maxNumber): for m, d in scalingTable: if maxNumber < m: return m, d return 0, 0 ################################################################################ # # Class DistribPanel # ################################################################################ class DistribPanel(GraphPanel): def __init__(self, parent, statsFrame, type): GraphPanel.__init__(self, parent, statsFrame) self.type = type if type == GRAPH_DISTRIB_BOTH: parent.SetTitle("Frequency Distribution") elif type == GRAPH_DISTRIB_NAUSEOUS: parent.SetTitle("Frequency Distribution (Nauseators)") elif type == GRAPH_DISTRIB_NON_NAUSEOUS: parent.SetTitle("Frequency Distribution (Non-Nauseators)") ############################################################################ def OnPaint(self, evt): dc = wx.PaintDC(self) # We can't draw the Y axis without data to scale to! if len(self.statsFrame.valids) == 0: return self.ChooseColoursDrawAxes(dc) # Go through the entries counting the number of entries in each 1s band. counts = [0 for x in range(10)] scalingCounts = [0 for x in range(10)] for e in self.statsFrame.valids: band = e.before / 1000 scalingCounts[band] = scalingCounts[band] + 1 if ((self.type == GRAPH_DISTRIB_BOTH) or (self.type == GRAPH_DISTRIB_NAUSEOUS and e.isNauseous) or (self.type == GRAPH_DISTRIB_NON_NAUSEOUS and not e.isNauseous)): counts[band] = counts[band] + 1 # Find the biggest band and scale the Y axis. maxScale, division = self.SelectScaleAndDivision(max(scalingCounts)) if maxScale == 0: return self.DrawScaleX(dc, 50, 0, 10, 1, 0, "Before (s)") self.DrawScaleY(dc, maxScale, division, "Respondents") # Draw the blocks. self.DrawHistogram(dc, counts, maxScale) ################################################################################ # # Class StressorsDistribPanel # ################################################################################ class StressorsDistribPanel(GraphPanel): def __init__(self, parent, statsFrame): GraphPanel.__init__(self, parent, statsFrame) parent.SetTitle("Frequency Distribution (Stressors)") ############################################################################ def OnPaint(self, evt): dc = wx.PaintDC(self) # We can't draw the Y axis without data to scale to! if len(self.statsFrame.valids) == 0: return self.ChooseColoursDrawAxes(dc) # Go through the entries counting the number of entries in each band. counts = [0 for x in range(31)] for e in self.statsFrame.valids: counts[e.GetStressScore() + 15] += 1 # Find the biggest band and scale the Y axis. maxScale, division = self.SelectScaleAndDivision(max(counts)) if maxScale == 0: return self.DrawScaleX(dc, 500 / 31, -15, 16, 5, 15, "Chill Points") self.DrawScaleY(dc, maxScale, division, "Respondents") # Draw the blocks. self.DrawHistogram(dc, counts, maxScale) ################################################################################ # # Class AgeDistribPanel # ################################################################################ class AgeDistribPanel(GraphPanel): def __init__(self, parent, statsFrame): GraphPanel.__init__(self, parent, statsFrame) parent.SetTitle("Frequency Distribution (Age)") ############################################################################ def OnPaint(self, evt): dc = wx.PaintDC(self) # We can't draw the Y axis without data to scale to! if len(self.statsFrame.valids) == 0: return self.ChooseColoursDrawAxes(dc) # Go through the entries counting the number of entries in each band. counts = [0 for x in range(10)] for e in self.statsFrame.valids: counts[e.ageGroup] += 1 # Find the biggest band and scale the Y axis. maxScale, division = self.SelectScaleAndDivision(max(counts)) if maxScale == 0: return self.DrawScaleX(dc, 50, 0, 10, 1, 0, "Age (Decade)") self.DrawScaleY(dc, maxScale, division, "Respondents") # Draw the blocks. self.DrawHistogram(dc, counts, maxScale) ################################################################################ # # Class DrugDistribPanel # ################################################################################ class DrugDistribPanel(GraphPanel): def __init__(self, parent, statsFrame): GraphPanel.__init__(self, parent, statsFrame) parent.SetTitle("Frequency Distribution Per Drug") ############################################################################ def OnPaint(self, evt): dc = wx.PaintDC(self) # We can't draw the Y axis without data to scale to! if len(self.statsFrame.valids) == 0: return self.ChooseColoursDrawAxes(dc) # Go through the entries counting the number of entries in each 1s band. counts = [ [ 0 for x in range(10) ] for d in range(len(pharmaData)) ] for e in self.statsFrame.valids: for drug in range(len(pharmaData)): if e.drugs[drug]: counts[drug][e.before / 1000] += 1 # Find the biggest band and scale the Y axis. maxScale, division = self.SelectScaleAndDivision(max(map(max, counts))) if maxScale == 0: return self.DrawScaleX(dc, 50, 0, 10, 1, 0, "Before (s)") self.DrawScaleY(dc, maxScale, division, "Respondents") # Draw the blocks. pixelsPerNumber = 500 / maxScale for drug in range(len(pharmaData)): points = [] for count in range(10): points.append(wx.Point(100 + (count * 50) + 25, 500 - counts[drug][count] * 500 / maxScale)) dc.SetPen(wx.Pen(pharmaData[drug][1])) dc.SetTextForeground(pharmaData[drug][1]) dc.DrawText(pharmaData[drug][0], 400, 100 + (drug * 30)) dc.DrawSpline(points) ################################################################################ # # Class StressorsByScorePanel # ################################################################################ class StressorsByScorePanel(GraphPanel): def __init__(self, parent, statsFrame, band): GraphPanel.__init__(self, parent, statsFrame) self.band = band if band == GRAPH_ALL: parent.SetTitle("Stressors By Score (All)") elif band == GRAPH_WORST: parent.SetTitle("Stressors By Score (Worst Third)") elif band == GRAPH_BEST: parent.SetTitle("Stressors By Score (Best Third)") elif band == GRAPH_MIDDLE: parent.SetTitle("Stressors By Score (Central Third)") ############################################################################ def OnPaint(self, evt): dc = wx.PaintDC(self) self.ChooseColoursDrawAxes(dc) division = 500 / 31 self.DrawScaleX(dc, division, -15, 16, 5, 15, "Chill Points") self.DrawScaleY(dc, 10, 1, "Before (s)") # For each stress score, go through the enttries and put them in a # vector. Sort the vector by score and select the Start and Length of # entries that we are going to consider for the graph type. for stressBand in range(-15, 16): bologians = self.ExtractStressBand(stressBand, self.band) p = len(bologians) for i, e in enumerate(bologians): if self.band == GRAPH_ALL: if i < partitions[GRAPH_MIDDLE][p % 3](p): dc.SetPen(wx.Pen("green")) dc.SetBrush(wx.Brush("green")) elif i < partitions[GRAPH_WORST][p % 3](p): dc.SetPen(wx.Pen("cyan")) dc.SetBrush(wx.Brush("cyan")) else: dc.SetPen(wx.Pen("red")) dc.SetBrush(wx.Brush("red")) x = e.GetStressScore() y = e.before dc.DrawCircle(100 + 250 + (x * division), 500 - (y / 20), 3) ################################################################################ # # Class StressorsByAvScorePanel # ################################################################################ class StressorsByAvScorePanel(GraphPanel): def __init__(self, parent, statsFrame, band): GraphPanel.__init__(self, parent, statsFrame) self.band = band if band == GRAPH_ALL: parent.SetTitle("Stressors By Average Score (All)") elif band == GRAPH_WORST: parent.SetTitle("Stressors By Average Score (Worst Third)") elif band == GRAPH_BEST: parent.SetTitle("Stressors By Average Score (Best Third)") elif band == GRAPH_MIDDLE: parent.SetTitle("Stressors By Average Score (Central Third)") ############################################################################ def OnPaint(self, evt): dc = wx.PaintDC(self) self.ChooseColoursDrawAxes(dc) division = 500 / 31 self.DrawScaleX(dc, division, -15, 16, 5, 15, "Chill Points") self.DrawScaleY(dc, 10, 1, "Before (s)") # For each stress score, go through the enttries and put them in a # vector. Sort the vector by score and select the Start and Length of # entries that we are going to consider for the graph type. workingSet = [] for stressBand in range(-15, 16): workingSet.extend(self.ExtractStressBand(stressBand, self.band)) self.ComputeAndPlotMeansAndSDs(dc, workingSet, lambda e: e.GetStressScore(), lambda e: e.before, "black", 250, division, 20.0, 0, 5) # Calculate the Spearman correlation. if len(workingSet) > 0: r = Rank(workingSet, lambda e: e.GetStressScore(), lambda e: e.before) dc.DrawText(r.spearman, 300, 50); ################################################################################ # # Class StressorsByAvAgePanel # ################################################################################ class StressorsByAvAgePanel(GraphPanel): def __init__(self, parent, statsFrame, band): GraphPanel.__init__(self, parent, statsFrame) self.band = band if band == GRAPH_ALL: parent.SetTitle("Stressors By Average Age (All)") elif band == GRAPH_WORST: parent.SetTitle("Stressors By Average Age (Worst Third)") elif band == GRAPH_BEST: parent.SetTitle("Stressors By Average Age (Best Third)") elif band == GRAPH_MIDDLE: parent.SetTitle("Stressors By Average Age (Central Third)") ############################################################################ def OnPaint(self, evt): dc = wx.PaintDC(self) self.ChooseColoursDrawAxes(dc) division = 500 / 31 self.DrawScaleX(dc, division, -15, 16, 5, 15, "Chill Points") self.DrawScaleY(dc, 10, 1, "Age (Decade)") # For each stress score, go through the enttries and put them in a # vector. Sort the vector by score and select the Start and Length of # entries that we are going to consider for the graph type. workingSet = [] for stressBand in range(-15, 16): workingSet.extend(self.ExtractStressBand(stressBand, self.band)) self.ComputeAndPlotMeansAndSDs(dc, workingSet, lambda e: e.GetStressScore(), lambda e: e.ageGroup, "black", 250, division, 1.0 / 50.0, 0, 5) # Calculate the Spearman correlation. if len(workingSet) > 0: r = Rank(workingSet, lambda e: e.GetStressScore(), lambda e: e.ageGroup) dc.DrawText(r.spearman, 300, 50); ################################################################################ # # Class ExercisesByAvScorePanel # ################################################################################ class ExercisesByAvScorePanel(GraphPanel): def __init__(self, parent, statsFrame): GraphPanel.__init__(self, parent, statsFrame) parent.SetTitle("Exercises By Average Score") ############################################################################ def OnPaint(self, evt): dc = wx.PaintDC(self) self.ChooseColoursDrawAxes(dc) division = 500 / 18 self.DrawScaleX(dc, division, 0, 18, 1, 0, "Number of Exercises") self.DrawScaleY(dc, 10, 1, "Red: Before Green: After (s)") # For exercises the working set contains all valid entries with an # after score. workingSet = [ e for e in self.statsFrame.valids if e.after > 0 ] self.ComputeAndPlotMeansAndSDs(dc, workingSet, lambda e: e.GetExerciseScore(), lambda e: e.before, "red", 0, division, 20.0, -3, 3) self.ComputeAndPlotMeansAndSDs(dc, workingSet, lambda e: e.GetExerciseScore(), lambda e: e.after, "green", 0, division, 20.0, 3, 3) ################################################################################ # # Class AgeByAvScorePanel # ################################################################################ class AgeByAvScorePanel(GraphPanel): def __init__(self, parent, statsFrame, band): GraphPanel.__init__(self, parent, statsFrame) self.band = band if band == GRAPH_ALL: parent.SetTitle("Age By Average Score (All)") elif band == GRAPH_WORST: parent.SetTitle("Age By Average Score (Worst Third)") elif band == GRAPH_BEST: parent.SetTitle("Age By Average Score (Best Third)") elif band == GRAPH_MIDDLE: parent.SetTitle("Age By Average Score (Central Third)") ############################################################################ def OnPaint(self, evt): dc = wx.PaintDC(self) self.ChooseColoursDrawAxes(dc) self.DrawScaleX(dc, 50, 0, 10, 1, 0, "Age (Decade)") self.DrawScaleY(dc, 10, 1, "Before (s)") # For age groups the working set contains all valid entries aged < 100. workingSet = [] for stressBand in range(-15, 16): workingSet.extend(self.ExtractStressBand(stressBand, self.band)) self.ComputeAndPlotMeansAndSDs(dc, workingSet, lambda e: e.ageGroup, lambda e: e.before, "black", 0, 50, 20.0, 0, 5) # Calculate the Spearman correlation. if len(workingSet) > 0: r = Rank(workingSet, lambda e: e.ageGroup, lambda e: e.before) dc.DrawText(r.spearman, 300, 50); ################################################################################ # # Class Rankable # ################################################################################ class Rankable: def __init__(self, x, y): self.valueX = x self.valueY = y self.rankX = 0.0 self.rankY = 0.0 ################################################################################ # # Class Rank # ################################################################################ class Rank: def __init__(self, workingSet, getBand, getScore): self.table = [] for e in workingSet: self.table.append(Rankable(float(getBand(e)), float(getScore(e)) / 1000.0)) self.Calculate() ############################################################################ def Calculate(self): # First sort by the Y value, so that we can assign rankings. self.table.sort(lambda a, b: cmp(a.valueY, b.valueY)) # Now step through the Y values, identifying blocks which are equal and # assigning Y rankings. Equivalent blocks share median values. Note that # ranking start at 1. count = 0 while count < len(self.table): end = 1 while count + end < len(self.table) and \ self.table[count + end].valueY == self.table[count].valueY: end = end + 1 median = float((2 * count) + (end - 1)) / 2.0; for blockCount in range(end): self.table[count + blockCount].rankY = median + 1.0; count = count + end # Now sort by the X value, so that we can assign rankings. self.table.sort(lambda a, b: cmp(a.valueX, b.valueX)) # Now step through the X values, identifying blocks which are equal and # assigning X rankings. Equivalent blocks share median values. Note that # ranking start at 1. count = 0 while count < len(self.table): end = 1 while count + end < len(self.table) and \ self.table[count + end].valueX == self.table[count].valueX: end = end + 1 median = float((2 * count) + (end - 1)) / 2.0; for blockCount in range(end): self.table[count + blockCount].rankX = median + 1.0; count = count + end # Now sum the squares of the differences between the ranks for Spearman. e = 0.0; n = float(len(self.table)) for count in range(len(self.table)): diff = self.table[count].rankX - self.table[count].rankY e = e + (diff * diff) valueS = (1.0 - ((6.0 * e) / (n * ((n * n) - 1)))) absValueS = abs(valueS) self.spearman = "Spearman rho = " + str(valueS) if absValueS > 0.5: self.spearman = self.spearman + " (Large)" elif absValueS > 0.3: self.spearman = self.spearman + " (Medium)" elif absValueS > 0.1: self.spearman = self.spearman + " (Small)" else: self.spearman = self.spearman + " (None)" ################################################################################ # # Startup # ################################################################################ app = EftApp() app.MainLoop()