graxxia-1.0.1/000077500000000000000000000000001255110771500131605ustar00rootroot00000000000000graxxia-1.0.1/LICENSE000066400000000000000000000260731255110771500141750ustar00rootroot00000000000000Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.graxxia-1.0.1/NOTICE.txt000077500000000000000000000001241255110771500147020ustar00rootroot00000000000000Graxxia, Groovy Maths Utils Copyright 2014, Simon Sadedin and Contributing Authors graxxia-1.0.1/README.md000066400000000000000000000131421255110771500144400ustar00rootroot00000000000000Graxxia ======= Graxxia is a library adds data analysis features to Groovy, similar to languages such as R, Julia, etc, and also to libraries such as Pandas for Python. It should be emphasised that there is nearly nothing in Graxxia itself that is not just a wrapper around other existing libraries. For example Matrix support all comes from Apache commons-math, parsing of CSV and TSV (tab separated) files comes from OpenCSV and GroovyCSV, etc. What can you do with Graxxia? You can get just a few interesting things: * An approximate equivalent to data frames in R * Read and write tab separated data files * Simple statistics computations * A prototype (very alpha) imitation of the linear modelling expression syntax in R (y ~ x + z, etc) Obviously this is not even remotely comparable to what is available for data analysis in the other languages mentioned above. However these classes can make doing simple data analysis in Groovy quite powerful and in some cases compares very favorably to doing it in other languages. Combined with the Groovy Shell, it becomes quite useful for interactive data analysis. Quick Try Out ============ Graxxia is now available from Maven Central! That means you can use it without any download or compile via the @Grab annotation. To give Graxxia a try, start a Groovy shell: groovysh Then enter the following: groovy:000> @Grab('org.graxxia:graxxia:0.9') groovy:001> import graxxia.* groovy:001> m = new Matrix([[3,4,5],[6,2,3]]) ===> 2x3 Matrix: 0: 3 4 5 1: 6 2 3 The @Grab line may take a while depending on whether you already downloaded any of Graxxia's dependencies or not. It will only be slow the first time you run it. Building and Using It from Source ================================= It's pretty straight forward: Check out the code: git clone https://github.com/ssadedin/graxxia.git Build it: cd graxxia ./gradlew jar Put it in your classpath. A nice way to play around with it is with groovysh. So, for example: groovysh -cp build/libs/graxxia.jar Note: this build process builds a fully self-contained jar file that contains all dependencies (sometimes called a 'fat' jar). In rare situations these could conflict with other libraries already in your class path. Note: if you encounter problems, please check that your Groovy version matches that specified in the build.gradle file. Graxxia should work with versions of Groovy > 2.2, but problems can occur if your version doesn't match (this can happen even between point releases of the same major version, unforutnately). Examples ========== The heart of Graxxia is the Matrix class. Start by importing the Graxxia classes: groovy:000> import graxxia.* Now make a simple Matrix: groovy:000> m = [[2,3,4],[8,6,2],[10,4,3]] as Matrix ===> 3x3 Matrix: 0: 2.0, 3.0, 4.0 1: 8.0, 6.0, 2.0 2: 10.0, 4.0, 3.0 So straight away you can see, there are some conversions between arrays, and lists of doubles that are built in for you. In general you can just take any 2 dimensional array / list combination and turn it into a Matrix. In fact, all Graxxia cares about is that they are Iterable, so you can even feed a Matrix dynamically if you like. We can access the elements, as you would expect, via indexing with row and column respectively: groovy:000> m[2][1] ===> 4.0 An important thing to point out here is that everything is using zero-based indexing, not 1-based. A whole row is accessed by omitting the second index: groovy:000> m[2] as String ===> [10.0, 4.0, 3.0] A whole column is accessed by omitting the first index: groovy:000> m[][1] ===> [3.0,6.0,4.0] Graxxia's Matrix class returns a special MatrixColumn object for column access. You can treat it as a List, but it is actually working under the covers to reflect accessors back into the original Matrix from which the column came. That is, you are not looking at a copy of the data, you are looking at the actual data. No copy is made when you access by column, even thought the data is stored natively in row format. Like R's data frames, Graxxia lets you give columns names: groovy:000> m.@names = ["x","y","z"] ===> [x, y, z] We can now see these when we display the matrix: groovy:000> m ===> 3x3 Matrix: x y z 0: 2.0, 3.0, 4.0 1: 8.0, 6.0, 2.0 2: 10.0, 4.0, 3.0 Matrices are Expandos, a feature which allows you to add non-numeric columns to a Matrix: groovy:000> m.animal = ["cat","dog","horse"] ===> [cat, dog, horse] groovy:000> m ===> 3x3 Matrix: animal x y z 0: cat 2.0, 3.0, 4.0 1: dog 8.0, 6.0, 2.0 2: horse 10.0, 4.0, 3.0 Matrices are Iterable objects, so they get all the normal Groovy magic methods that are available on Iterators. For example, we can 'grep' a Matrix in a useful way: groovy:000> m.grep { x > 2 }.animal ===> [dog, horse] You can load and save matrices and they remember their column names: m.save("test.tsv") n = Matrix.load("test.tsv") Of course, matrix algebra itself works and all the scalar and matrix operators do what you (hopefully) expect: groovy:000> m = m + m ===> 3x3 Matrix: 0: 4, 6, 8 1: 16, 12, 4 2: 20, 8, 6 groovy:000> m = m * 2 ===> 3x3 Matrix: 0: 8, 12, 16 1: 32, 24, 8 2: 40, 16, 12 The Stats class is a wrapper for the Commons-Math DescriptiveStatistics class. It will pull anything from an Iterable and give you back statistics about it, including rows and columns from a Matrix: m.collect { Stats.from(it).mean } graxxia-1.0.1/build.gradle000077500000000000000000000022211255110771500154370ustar00rootroot00000000000000apply plugin: 'groovy' project.ext { VERSION="0.0.1" STAGE="build/stage/graxxia-$VERSION" } repositories { mavenCentral() } configurations { compile } dependencies { groovy group: 'org.codehaus.groovy', name: 'groovy', version: '2.3.11' groovy group: 'org.apache.ivy', name:'ivy', version:'2.2.0' groovy 'com.lowagie:itext:2.1.7' groovy 'org.apache.commons:commons-math3:3.5' groovy 'junit:junit:4.4' groovy 'commons-cli:commons-cli:1.1' groovy 'com.xlson.groovycsv:groovycsv:1.0' groovy 'log4j:log4j:1.2.17' compile files(fileTree(dir:'lib', includes:['*.jar'])) } // Bundle all dependencies into output library jar { from configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } exclude "groovy/**" exclude "org/codehaus/**" exclude "META-INF/groovy*" exclude "META-INF/**/*groovy*" exclude "META-INF/BCKEY*" exclude "org/bouncycastle/**" } groovydoc { } task doc << { groovydoc { packageNames = [''] destinationDir = new File('doc') source = 'src/main/groovy' } } task wrapper(type: Wrapper) { gradleVersion = '1.4' } graxxia-1.0.1/src/000077500000000000000000000000001255110771500137475ustar00rootroot00000000000000graxxia-1.0.1/src/main/000077500000000000000000000000001255110771500146735ustar00rootroot00000000000000graxxia-1.0.1/src/main/groovy/000077500000000000000000000000001255110771500162205ustar00rootroot00000000000000graxxia-1.0.1/src/main/groovy/graxxia/000077500000000000000000000000001255110771500176635ustar00rootroot00000000000000graxxia-1.0.1/src/main/groovy/graxxia/Drawing.groovy000077500000000000000000000357501255110771500225420ustar00rootroot00000000000000/* * Graxxia - Groovy Maths Utililities * * Copyright (C) 2014 Simon Sadedin, ssadedingmail.com and contributors. * * This file is licensed under the Apache Software License Version 2.0. * For the avoidance of doubt, it may be alternatively licensed under GPLv2.0 * and GPLv3.0. Please see LICENSE.txt in your distribution directory for * further details. */ package graxxia import groovy.transform.CompileStatic; import java.awt.BasicStroke import java.awt.Font import java.awt.FontMetrics import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Color; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import org.apache.commons.math3.analysis.UnivariateFunction import org.apache.commons.math3.analysis.interpolation.* import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction; import javax.imageio.* /** * Drawing is a very simple graphics class that enables simple line and text drawings on a * virtualised canvas that automatically maps mathematical coordinates to pixel based * coordinates. * * @author simon */ class Drawing { static enum Coord { PIXELS, PLOT } BufferedImage image = null; Graphics2D g = null String fileName = null List operations = [] boolean autoSave = true int xOffset = 0 int yOffset = 0 double minY = 0 double maxY = 0 List minX = [] List maxX = [] double xScale = 1.0d double yScale = 1.0d int height = -1 int width = -1 boolean log = false Font font = null FontMetrics fontMetrics = null /** * The default coordinate mode is to specify points in plot coordinates, * as opposed to pixels. */ Coord coordType = Coord.PLOT static float [] dash1 = [5.0f] as float[]; /** * The default line style used for dashed lines */ BasicStroke DASHED = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash1, 0.0f); /** * The default line style used for solid lines */ BasicStroke SOLID = new BasicStroke(1.0f) /** * The default y position at which the next line of text will be drawn */ double textY = 0 /** * The default x position at which the next line of text will be drawn */ double textX = 0 /** * The height of the current font in drawing coordinates (not pixels) */ double textHeight = 0 int xMargin = 0 int yMargin = 0 public Drawing(String fileName, int width, int height, double minX, double minY, double maxX, double maxY) { this(fileName, width, height, [minX], minY, [maxX], maxY) } public Drawing(String fileName, int width, int height, List minX, double minY, List maxX, double maxY) { this.fileName = fileName this.minX = minX this.maxX = maxX this.minY = minY this.maxY = maxY this.height = height this.width = width this.init() } void setMargin(int x, int y) { this.xMargin = x this.yMargin = y this.init() } void init() { this.image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); this.g = image.createGraphics() this.xScale = (width-2*xMargin) / ([maxX,minX].transpose().collect { it[0] - it[1] }.sum() ) this.yScale = (height-2*yMargin) / (maxY - minY) // invert Y so it is like math plotting coordinates this.xOffset = -(minX.min()) this.yOffset = -minY g.setBackground(Color.WHITE) g.clearRect(0,0,width,height) color(0,0,0) g.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); setFont("SansSerif", Font.PLAIN, 12); textY = maxY } public Drawing(String fileName, int width, int height) { this(fileName,width,height,0,0, 1.0d, 1.0d) } Drawing fontSize(float value) { this.setTextFont(this.font.deriveFont(value)) } Drawing setFont(String name, int style, int size) { setTextFont(new Font(name, style,size)) } Drawing setTextFont(Font f) { this.font = f g.setFont(this.font) this.fontMetrics = g.getFontMetrics() int fontHeight = this.fontMetrics.getHeight() this.textHeight = this.unTxY(fontHeight) - this.unTxY(0d) if(log) log.println "Text height = $textHeight" return this } /** * Adjust xScale and yScale to fit the given point inside * the bounds */ void rescale(double x1, double y1, double x2, double y2) { } Drawing bars(def x1, def x2, def y, List overText=null, List underText=null) { lines(x1,y,x2,y,bars:true,overText:overText, underText:underText) } Drawing bar(def x1, def x2, def y, String overText=null, String underText=null) { bars([x1], [x2], [y], [overText], [underText]) } Drawing bar(IntRange range, def y, String overText=null, String underText=null) { bar(range.from, range.to, y, overText, underText) } PolynomialSplineFunction loess(Map options=[:], Iterable x, Iterable y) { List xValues = new ArrayList() List yValues = new ArrayList() Iterator yIterator = y.iterator() for(xValue in x) { Double yValue = yIterator.next() if(xValue == null || yValue == null) continue if(yValue instanceof Double && (yValue.isNaN() || yValue.isInfinite())) continue if(xValue instanceof Double && (xValue.isNaN() || xValue.isInfinite())) continue xValues.add(xValue) yValues.add(yValue) } List lineYValues = [] List lineXValues = [] UnivariateInterpolator interp if(xValues.size()==1) { return new UnivariateInterpolator() { UnivariateFunction interpolate(double [] xval, double [] yval) { return new UnivariateFunction() { double value(double xValue) { yValues[0] } } } } } else if(xValues.size() > 8) { interp = new LoessInterpolator() } else { interp = new LinearInterpolator() } PolynomialSplineFunction fn = interp.interpolate(xValues as double[], yValues as double[]) double minXValue = x.min() double maxXValue = x.max() // Interpolate to factor of 10 double width = maxXValue - minXValue // A point every 5 pixels double xInterval = 5 / xScale double xInterp = minXValue List colors = [] while(xInterp < maxXValue) { lineXValues << xInterp double yValue = fn.value(xInterp) lineYValues << yValue if(options.color != null) colors << options.color(xInterp, yValue) xInterp += xInterval } if(options.color != null) { options.color = colors; } if(lineXValues.size()>1) lines(options, lineXValues[0..-2], lineYValues[0..-2], lineXValues[1..-1], lineYValues[1..-1]) return fn } Drawing lines(Map options=[:], def x1, def y1, def x2, def y2) { if(x1 instanceof List ||x1 instanceof double[]) { if(x1.size() != y1.size()) throw new IllegalArgumentException("Size of x1 (${x1.size()} different to size of y1 ${y1.size()}") if(x2.size() != y2.size()) throw new IllegalArgumentException("Size of x2 (${x2.size()} different to size of y2 ${y2.size()}") } x1.eachWithIndex { x, index -> if(options.color) { if(options.color instanceof Closure) color(options.color(x, y1[index], x2[index], y2[index])) else if(options.color instanceof List) color(options.color[index]) else color(options.color) } def txed = txLine(x, y1[index], x2[index], y2[index]) if(options.bars) { drawBar(txed, options.overText?options.overText[index]:null, options.underText?options.underText[index]:null) if(options.overText && options.overText[index]) { // Draw text centered over bar with baseline 1 pixel above bar int textWidth = fontMetrics.stringWidth(options.overText[index]) int middleX = txed[0] + (txed[2]-txed[0])/2 int textXPos = middleX - textWidth/2 g.drawString(options.overText[index],(float)textXPos, (float)(txed[1] - 4)) } if(options.underText && options.underText[index]) { // Draw text centered over bar with baseline 1 pixel above bar int textWidth = fontMetrics.stringWidth(options.underText[index]) int middleX = txed[0] + (txed[2]-txed[0])/2 int textXPos = middleX - textWidth/2 g.drawString(options.underText[index],(float)textXPos, (float)(txed[1] + fontMetrics.height+1)) } } } if(autoSave) save() return this } int barHeightUp = 5 int barHeightDown = 5 private void drawBar(lineCoords,String overText=null, String underText=null) { g.drawLine((int)lineCoords[0],(int)lineCoords[1]-barHeightUp,(int)lineCoords[0],(int)lineCoords[1]+barHeightDown) g.drawLine((int)lineCoords[2],(int)lineCoords[3]-barHeightUp,(int)lineCoords[2],(int)lineCoords[3]+barHeightDown) } Drawing line(def x1, def y1, def x2, def y2) { // println "line ($x1,$y1) - ($x2,$y2)" txLine(x1,y1,x2,y2) if(autoSave) save() return this } Drawing color(List rgb) { color(rgb[0],rgb[1], rgb[2]) } Drawing color(String value) { Color colorVal = Color[value] g.setPaint(colorVal) return this } Drawing color(int red, int green, int blue) { g.setPaint(new Color(red,green,blue)) return this } List txLine(double x1, double y1, double x2, double y2) { double x1Tx = txX(x1) double x2Tx = txX(x2) y1 = txY(y1) y2 = txY(y2) if(log) println "Drawing line ($x1-$x2) ($x1Tx,$y1) - ($x2Tx,$y2)" g.drawLine((int)x1Tx, (int)y1, (int)x2Tx, (int)y2) [x1Tx,y1,x2Tx,y2] } void drawRegions() { [minX,maxX].transpose().each { region -> line(region[0],minY,region[0],maxY) line(region[1],minY,region[1],maxY) } } void drawBorder() { g.drawRect(xMargin-1, yMargin-1, width-2*xMargin+2, height-2*yMargin+2) } Double txX(double x) { if(coordType == Coord.PIXELS) return x double startOffset = xMargin; int xInterval = 0 while(x > maxX[xInterval]) { startOffset += (xScale * (maxX[xInterval] - minX[xInterval])) ++xInterval if(xInterval >= maxX.size() -1) break } if(xInterval >= minX.size()) xInterval = minX.size()-1 Double result = startOffset + xScale *(Math.max(x - minX[xInterval],0)) // println "$x => $result" return result } Double txY(double y) { if(coordType == Coord.PIXELS) return y (this.height - yMargin) - yScale * (yOffset + y) } @CompileStatic double unTxY(double y) { (this.height - y) / yScale - yOffset } @CompileStatic double unTxX(double x) { x / xScale - xOffset } void save() { ImageIO.write(image, "png", new File(fileName)); } void save(Closure c) { boolean oldAutoSave = this.autoSave this.autoSave = false c() save() this.autoSave = oldAutoSave } /** * Draws text at the given x,y position in graph coordinates (not pixels) *

* The text appears with its base line at the given y coordinate, ie: the * y position you specify is the bottom of the text. If you want to specify the * top of the text, use the #textHeight property to pass y+textHeight */ Drawing text(def x, def y, def value) { x = x.toDouble() y = y.toDouble() textY = y + this.textHeight textX = x value = String.valueOf(value) x = txX(x) y = txY(y) if(log) println "Drawing $value at ($x,$y)" g.drawString(value, (float)x, (float)y) if(autoSave) save() } /** * Draw text at the default next position. * The default next position is adjusted each time new text is output so that * if you do nothing, the text appears as evenly spaced lines at the current * font height. * * @param value * @return */ Drawing text(def value) { double x = textX.toDouble() double y = textY.toDouble() text(x,y,value) } Drawing drawYAxis(List yAxis, boolean grid=false) { def yTickPoints = yAxis.collect { txY(it) } save { g.setStroke(DASHED) lines([minX[0]] * (yAxis.size()-2), yAxis[1..-2], [maxX[-1]]*(yAxis.size()-2), yAxis[1..-2]) g.setStroke(SOLID) color("black") coordType = Drawing.Coord.PIXELS [yAxis, yTickPoints].transpose().each { text(xMargin-40, it[1], it[0]) } } coordType = Drawing.Coord.PLOT return this } static void main(String [] args) { Drawing d = new Drawing("/Users/simon/test.png", 800,600, 10,0, 20, 10) d.log = true Matrix m = new Matrix([ [2,0,0,3], [5,0,7,2] ]) println "Drawing $m" /* d.lines(m[][0], m[][1], m[][2], m[][3]) .color(255,0,0) .line(5,3,2,4) */ d.with { color(0,255,0) line(10,0,14,4) bars([12],[13.5],[1]) text(12,1, "hello") } } } graxxia-1.0.1/src/main/groovy/graxxia/IntegerStats.java000077500000000000000000000071551255110771500231550ustar00rootroot00000000000000package graxxia; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Arrays; import org.apache.commons.math3.stat.descriptive.SummaryStatistics; /** * An efficient method to calculate percentiles of integer values * that doesn't require holding them all in memory or sorting them. *

* It relies on some limitations regarding coverage values: *

  • They are integers *
  • We assume bounds on the range, and don't care about it * above a certain value. For example, we are unlikely to observe * values above 1000 and those that do are unlikely to affect the * 50'th percentile. * * @author simon.sadedin@mcri.edu.au */ public class IntegerStats extends SummaryStatistics { private static final long serialVersionUID = 1L; public int [] values = null; int total = 0; /** * * @param maxPercentileValue */ public IntegerStats(int maxPercentileValue) { values = new int[maxPercentileValue]; Arrays.fill(values, 0); } public IntegerStats(int maxPercentileValue, InputStream inStream) throws IOException { values = new int[maxPercentileValue]; BufferedReader r = new BufferedReader(new InputStreamReader(inStream)); String line = null; while((line = r.readLine()) != null) { leftShift(line); } } public IntegerStats(int maxPercentileValue, Iterable covValues) { values = new int[maxPercentileValue]; for(Object obj : covValues) { this.leftShift(obj); } } void leftShift(Object obj) { if(obj instanceof Integer) { addValue((Integer)obj); } else if(obj instanceof String) { addValue(Integer.parseInt(String.valueOf(obj).trim())); } else if(obj instanceof Number) { addValue(((Number)obj).intValue()); } } /** * Count the given coverage value in calculating the median * @param coverage */ void addValue(int coverage) { if(coverage>=values.length) ++values[values.length-1]; else ++values[coverage]; super.addValue(coverage); ++total; } /** * Return the specified percentile from the observed coverage counts * eg: for median, getPercentile(50). * * @return the specified percentile, if it is smaller than the max value passed in the * constructor. */ int getPercentile(int percentile) { int observationsPassed = 0; int lowerValue = -1; final int medianIndex = (int)((float)total / (100f/(float)percentile)); for(int i=0; i= medianIndex) { if(total%2 == 0) { // Find the next value and return average of this one and that lowerValue = i; for(int k=i+1; k0) { return (lowerValue + k) / 2; } } } else return i; } } return -1; } public int getMedian() { return getPercentile(50); } public int getAt(int percentile) { return getPercentile(percentile); } public String toString() { return super.toString() + "Median: " + getMedian() + "\n"; } }graxxia-1.0.1/src/main/groovy/graxxia/IterationDelegate.groovy000077500000000000000000000014261255110771500245310ustar00rootroot00000000000000/* * Graxxia - Groovy Maths Utililities * * Copyright (C) 2014 Simon Sadedin, ssadedingmail.com and contributors. * * This file is licensed under the Apache Software License Version 2.0. * For the avoidance of doubt, it may be alternatively licensed under GPLv2.0 * and GPLv3.0. Please see LICENSE.txt in your distribution directory for * further details. */ package graxxia class IterationDelegate { Integer row = 0 Matrix host IterationDelegate(Matrix host) { this.host = host } def propertyMissing(String name) { def column = host.getProperty(name) if(column == null) { if(name in host.@names) { host.setProperty(name, host.col(host.@names.indexOf(name))) column = host[name] } } return column[row] } } graxxia-1.0.1/src/main/groovy/graxxia/Matrix.groovy000077500000000000000000000741411255110771500224100ustar00rootroot00000000000000/* * Graxxia - Groovy Maths Utililities * * Copyright (C) 2014 Simon Sadedin, ssadedingmail.com and contributors. * * This file is licensed under the Apache Software License Version 2.0. * For the avoidance of doubt, it may be alternatively licensed under GPLv2.0 * and GPLv3.0. Please see LICENSE.txt in your distribution directory for * further details. */ package graxxia import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import groovy.lang.Closure; import groovy.transform.CompileStatic; import org.apache.commons.math3.linear.Array2DRowRealMatrix import org.apache.commons.math3.linear.RealMatrix; import org.codehaus.groovy.runtime.typehandling.GroovyCastException; /** * Wraps an Apache-Commons-Math matrix of double values with a * Groovy interface for convenient access. Because it wraps the * underlying matrix as a delegate, all the original methods of * the Commons-Math implementation are available directly, along with * Groovy-enhanced versions. *

    * The most basic enhancements come in the form of random access operators * that allowthe Matrix class to be referenced using square-bracket notation: *

     * Matrix m = new Matrix(2,2,[1,2,3,4])
     * assert m[0][0] == 2
     * assert m[1][1] == 4
     * 
    * The rows of the Matrix are directly accessible simply by using * square-bracket indexing: *
     * assert m[0] == [1,2]
     * assert m[1] == [3,4]
     * 
    * The columns are accessed by using an empty first index: *
     * assert m[][0] == [1,3]
     * assert m[][1] == [2,4]
     * 
    * Rows and columns can both be treated as normal Groovy collections: *
     * assert m[0].collect { it.any { it > 2 }  } == [ false, true ]
     * assert m[][0].collect { it > 1 } == [ false, true ]
     * 
    * Note that in the above code, both row-wise and column-wise access * occurs without copying any data. *

    * Transforming the whole matrix can be done using transform: *

     * assert m.transform { it * 2 } == Matrix(2,2,[2,4,6,8])
     * 
    * As an option, row and column indexes are available as well: *
     * assert m.transform { value, row, column -> value * 2 } == Matrix(2,2,[2,4,6,8])
     * 
    * * @author simon.sadedin@mcri.edu.au */ class Matrix extends Expando implements Iterable, Serializable { static final long serialVersionUID = 0 static { // println "Setting Matrix meta class properties ...." double[][].metaClass.toMatrix = { new Matrix(delegate) } def originalMethod = double[][].metaClass.getMetaMethod("asType", Class) double[][].metaClass.asType = { arg -> arg == Matrix.class ? delegate.toMatrix() : originalMethod(arg)} def original2 = Array2DRowRealMatrix.metaClass.getMetaMethod("asType", Class) Array2DRowRealMatrix.metaClass.asType = { arg -> arg == Matrix.class ? new Matrix(arg) : original2(arg)} def originalMultiply = Integer.metaClass.getMetaMethod("multiply", Class) Integer.metaClass.multiply = { arg -> arg instanceof Matrix ? arg.multiply(delegate) : originalMultiply(arg)} } /** * How many rows are displayed in toString() and other calls that format output */ static final int DISPLAY_ROWS = 50 @Delegate Array2DRowRealMatrix matrix List names = [] public Matrix(int rows, int columns) { matrix = new Array2DRowRealMatrix(rows, columns) } public Matrix(MatrixColumn... sourceColumns) { this.initFromColumns(sourceColumns) } @CompileStatic private void initFromColumns(MatrixColumn[] sourceColumns) { MatrixColumn c0 = sourceColumns[0] int rows = c0.size() final int cols = sourceColumns.size() double[][] newData = new double[rows][] MatrixColumn [] columns = sourceColumns for(int i=0; i c.name } } Matrix(Map sourceColumns) { int rows = sourceColumns.iterator().next().value.size() final int cols = sourceColumns.size() double[][] newData = new double[rows][] List iters = sourceColumns.collect { it.value.iterator() } for(int i=0; i rows, List columnNames=null) { List data = new ArrayList(4096) int rowCount = 0 List isNumerics def r0 = rows[0] if(rows[0] instanceof Iterable) { isNumerics = r0.collect { it instanceof Number } } else { isNumerics = [true] * r0.size() } int matrixColumnCount = isNumerics.count { it } // Initialise an empty list for each non-numeric column that // we are going to fill List nonNumerics = isNumerics.grep { !it }.collect { [] } int rowIndex = 0 for(row in rows) { int colIndex = 0 int numericColumnIndex = 0 int nonNumericColumnIndex = 0 double[] rowNumericValues = new double[matrixColumnCount] for(value in row) { if(isNumerics[colIndex]) { rowNumericValues[numericColumnIndex++] = (double)value } else nonNumerics[nonNumericColumnIndex++].add(value) ++colIndex } ++rowIndex data.add(rowNumericValues) } matrix = new Array2DRowRealMatrix((double[][])data.toArray(), false) if(columnNames) this.@names = [columnNames,isNumerics].transpose().grep { it[1] }.collect { it[0] } int nonNumericIndex = 0 isNumerics.eachWithIndex { isNumeric, index -> if(!isNumeric) this.setProperty(columnNames[index],nonNumerics[nonNumericIndex++]) } } public Matrix(double [][] values) { matrix = new Array2DRowRealMatrix(values, false) } public Matrix(int rows, int columns, List data) { this.initFromList(rows,columns,data) } @CompileStatic void initFromList(int rows, int columns, List data) { matrix = new Array2DRowRealMatrix(rows, columns) int i=0 for(int r=0; r getColumns(List names) { new MatrixColumnList(columns:names.collect { this.names.indexOf(it) }.collect { int index -> assert index >= 0; col(index) }) } MatrixColumnList getColumns() { new MatrixColumnList(columns:(0.. *
  • Plain old indexing returns a row: m[4] returns 5th row of matrix. *
  • Double indexing returns a cell: m[4][5] returns 6th column of 4th row. *
  • Empty index returns a column: m[][6] returns 7th column *
  • List (or any iterable) index returns rows matching indices: * *
         * Matrix m = new Matrix([1..80], 10, 8)
         * m[2..4] == [ [ 9..16 ], [17..24], [25..32] ]
         * @param n
         * @return
         */
        @CompileStatic
        Object getAt(Object n) {
            if(n == null) {
                return getColumns()
            }
            else
            if(n instanceof Number)
                return matrix.dataRef[(int)n]
            else
            if(n instanceof List) {
                List l = (List)n
                if(l.size() == 0) // Seems to happen with m[][2] type syntax
                    return getColumns()
                else {
                    double [][] submatrix = subsetRows(l)
                    Matrix result = new Matrix(new Array2DRowRealMatrix(submatrix))
                    result.@names = this.@names
                    if(!this.properties.isEmpty()) 
                        this.transferPropertiesToRows(result, l)
                    return result
                }
            }
            else
            if(n instanceof Iterable) {
                return subsetRows((n))
            }
            else
            if(n.class.isArray()) {
                return subsetRows(n as Collection)
            }
            else {
                throw new IllegalArgumentException("Cannot subset rows by type: " + n?.class?.name)
            }
        }
        
        @CompileStatic
        Iterator iterator() {
           new Iterator() {
               
               int i=0
               final int numRows = matrix.rowDimension
               
               boolean hasNext() {
                   return i i) {
            List indices = new ArrayList(this.matrix.rowDimension)
            i.each { Number n -> indices.add(n.toInteger()) }
            
            double [][] result = new double[indices.size()][this.matrix.columnDimension]
            int destRowIndex = 0
            for(int srcRowIndex in indices) {
                System.arraycopy(this.matrix.dataRef[srcRowIndex], 0, result[destRowIndex++], 0, this.matrix.columnDimension)
            }
            return result
        }
        
        @CompileStatic
        void putAt(int n, Object values) {
           matrix.dataRef[n] = (values as double[])
        }
        
        /**
         * Iterates through a matrix row-wise, passing each row as an array of doubles
         * to the supplied closure.
         * 

    * If the passed closure acceptes 1 argument then just passes the row. * If 2 arguments, passes the row AND the index. *

    * In addition, this method enables accessing of columns and expando properties * by name for the current row. *

    * Example: *

         *  def m = new Matrix(x1: [1,2,3,4,5], x2: [2,4,6,8,10], x3:[7,6,5,4,3])
         *  assert m.collect { x1 * x2 } == [2.0d,  8.0d, 18.0d, 32.0d, 50.0d]
         *  
    * * Note: If you want to get a matrix back, see the #transformRows() method. * * @param c Closure to execute for each row in the matrix * @return results collected */ @CompileStatic List collect(Closure c) { List results = new ArrayList(matrix.dataRef.size()) IterationDelegate delegate = new IterationDelegate(this) boolean withDelegate = !this.properties.isEmpty() || this.@names if(withDelegate) { c = (Closure)c.clone() c.setDelegate(delegate) } int rowIndex = 0; if(c.maximumNumberOfParameters == 1) { for(double [] row in matrix.dataRef) { if(withDelegate) delegate.row = rowIndex++ results.add(c(row)) } } else if(c.maximumNumberOfParameters == 2) { for(double [] row in matrix.dataRef) { if(withDelegate) delegate.row = rowIndex results.add(c(row, rowIndex)) ++rowIndex } } return results } @CompileStatic public List findIndexValues(Closure c) { List keepRows = [] int rowIndex = 0; IterationDelegate delegate = new IterationDelegate(this) boolean withDelegate = !this.properties.isEmpty() if(withDelegate) { c = (Closure)c.clone() c.setDelegate(delegate) } if(c.maximumNumberOfParameters == 1) { for(double [] row in matrix.dataRef) { if(withDelegate) delegate.row = rowIndex if(c(row)) keepRows.add(rowIndex) ++rowIndex } } else if(c.maximumNumberOfParameters == 2) { for(double [] row in matrix.dataRef) { if(withDelegate) delegate.row = rowIndex if(c(row, rowIndex)) keepRows.add(rowIndex) ++rowIndex } } return keepRows } /** * Filter the rows of this matrix and return * a Matrix as a result * * @param c a Closure to evaluate * * @return Matrix for which the closure c returns a non-false value */ @CompileStatic Matrix grep(Closure c) { List keepRows = this.findIndexValues(c) double [][] submatrix = this.subsetRows((Iterable)keepRows) def result = new Matrix(new Array2DRowRealMatrix(submatrix)) result.@names = this.@names if(!this.properties.isEmpty()) this.transferPropertiesToRows(result, keepRows) return result } private void transferPropertiesToRows(Matrix result, List indices = null) { if(indices != null) { this.properties.each { String key, Iterable value -> result[key] = value[indices] } } else { this.properties.each { String key, Iterable value -> result[key] = value as List } } } /** * Transforms a matrix by processing each element through the given * closure. The closure must take either one argument or three arguments. * The one argument version is only passed data values, while the * three argument version is passed the data value and also the row and column * position. * * @param c A closure taking 1 or 3 arguments (data value, or data value, row, * column) * @return A matrix reflecting the transformed data values */ @CompileStatic Matrix transform(Closure c) { Matrix result if(c.maximumNumberOfParameters == 1) { result = transformWithoutIndices(c) } else if(c.maximumNumberOfParameters == 3) { result = transformWithIndices(c) } if(names) result.names = this.names if(!this.properties.isEmpty()) this.transferPropertiesToRows(result) return result } @CompileStatic private Matrix transformWithoutIndices(Closure c) { final int rows = matrix.rowDimension final int cols = matrix.columnDimension double[][] newData = new double[rows][cols] IterationDelegate delegate = new IterationDelegate(this) boolean withDelegate = !this.properties.isEmpty() if(withDelegate) { c = (Closure)c.clone() c.delegate = delegate } for(int i=0; i groupBy(Closure c) { IterationDelegate delegate = new IterationDelegate(this) boolean withDelegate = !this.properties.isEmpty() if(withDelegate) { c = (Closure)c.clone() c.setDelegate(delegate) } int rowIndex = 0; List myNames = this.@names matrix.dataRef.groupBy { if(withDelegate) delegate.row = rowIndex++ c() }.collectEntries { e -> def value = e.value def m = new Matrix(e.value) m.@names = myNames; [ e.key, m ] } } Map countBy(Closure c) { IterationDelegate delegate = new IterationDelegate(this) boolean withDelegate = !this.properties.isEmpty() if(withDelegate) { c = (Closure)c.clone() c.setDelegate(delegate) } int rowIndex = 0; List myNames = this.@names matrix.dataRef.countBy { if(withDelegate) delegate.row = rowIndex++ c() } } /** * Shorthand to give a familiar function to R users */ List which(Closure c) { this.findIndexValues(c) } Matrix multiply(double d) { new Matrix(this.matrix.scalarMultiply(d)) } Matrix multiply(Matrix m) { new Matrix(this.matrix.preMultiply(m.dataRef)) } Matrix plus(Matrix m) { new Matrix(this.matrix.add(m.matrix)) } Matrix minus(Matrix m) { new Matrix(this.matrix.subtract(m.matrix)) } Matrix divide(double d) { new Matrix(this.matrix.scalarMultiply(1/d)) } Matrix plus(double x) { new Matrix(this.matrix.scalarAdd(x)) } Matrix minus(double x) { new Matrix(this.matrix.scalarMinus(x)) } Matrix transpose() { new Matrix(this.matrix.transpose()) } Matrix div(Matrix m) { this.transform { value, i, j -> value / m[i][j] } } /** * Save the matrix to a file in tab separated format. *

    * If the matrix has column names set (either as expando properties or using the @names attribute, * then the column names will be printed as the first row. *

    * Options may be passed as names parameters, or a map of parameters as the first argument. * Valid options include: *

  • r - set to true to make the output format compatible with the default * format read by R * * @param fileName */ void save(Map options = [:], String fileName) { new File(fileName).withWriter { w -> save(options,w) } } void save(Map options = [:], Writer w) { List nonMatrixCols = this.properties*.key List columnNames = nonMatrixCols + (this.names?:this.properties.names) // NOTE: the this.properties.names seems to be required because of a // weird bug where groovy will prefer to set an expando property rather than // set the real property on this object if(columnNames) { if(!options.r) w.print "# " w.println columnNames.join("\t") } if(this.rowDimension == 0) return List adapters = TSV.formats + [ new NumberMatrixValueAdapter(), new StringMatrixValueAdapter() ] List types = nonMatrixCols.collect { colName -> adapters.find { adapter -> adapter.sniff(getProperty(colName)[0]) } } eachRow { row -> def d = delegate if(nonMatrixCols) { nonMatrixCols.eachWithIndex { colName, colIndex -> w.print((types[colIndex].serialize(d[colName])) + "\t") } } w.println((row as List).join("\t")) } } static Matrix load(String fileName) { List rows = new ArrayList(1024) Reader r = new FileReader(fileName) // Sniff the first line String firstLine = r.readLine() List names if(firstLine.startsWith('#')) { names = firstLine.substring(1).trim().split("\t") } else { r.close() r = new FileReader(fileName) } Matrix m = new Matrix(new TSV(readFirstLine:true, r)*.values, names) return m } String toString() { def headerCells = this.@names if(this.properties) { headerCells = this.properties.collect { it.key } + headerCells } int columnWidth = Math.max(10, headerCells ? headerCells*.size().max() : 0) int rowNumWidth = 6 String headers = headerCells ? (" " * rowNumWidth) + headerCells*.padRight(columnWidth).join(" ") + "\n" : "" DecimalFormat format = new DecimalFormat() format.minimumFractionDigits = 0 format.maximumFractionDigits = 6 int rowCount = 0 def printRow = { row -> List cells = (row as List) if(this.properties) { cells = this.properties.collect { it.value?it.value[rowCount]:"null" } + cells } def values = cells.collect { value -> if(!(value instanceof Double)) return String.valueOf(value).padRight(columnWidth) ((value < 0.0001d && value !=0 && value > -0.0001d) ? String.format("%1.6e",value) : format.format(value)).padRight(columnWidth) } return ((rowCount++) + ":").padRight(rowNumWidth) + values.join(" ") } if(matrix.rowDimension printRow(row) }.join("\n") } else { int omitted = matrix.rowDimension-DISPLAY_ROWS String value = "${matrix.rowDimension}x${matrix.columnDimension} Matrix:\n"+ headers + matrix.data[0..DISPLAY_ROWS/2].collect(printRow).join("\n") rowCount += omitted -1 value += "\n... ${omitted} rows omitted ...\n" + matrix.data[-(DISPLAY_ROWS/2)..-1].collect(printRow).join("\n") return value } } Writer toMarkdown(Writer w = null) { if(w == null) w = new StringWriter() def propCells = this.properties.collect { it.key } def headerCells = this.@names if(this.properties) { headerCells = propCells + headerCells } int columnWidth = Math.max(10, headerCells ? headerCells*.size().max() : 0) int rowNumWidth = 6 List columnWidths if(this.rowDimension<100) { columnWidths = properties.collect { e -> e.value[0] instanceof Number ? columnWidth : e.value.collect { String.valueOf(it).size()}.max() } + [columnWidth] * this.columnDimension } else { columnWidths = columnWidth * this.columnDimension } String headers = headerCells ? (" " * rowNumWidth) + headerCells*.padRight(columnWidth).join(" ") + "\n" : "" DecimalFormat format = new DecimalFormat() format.minimumFractionDigits = 0 format.maximumFractionDigits = 6 int columnIndex = 0 String header = "| " + headerCells.collect { it.padRight(columnWidths[columnIndex++]) }.join(" | ") + "|" w.println(header) columnIndex = 0 w.println "|-" + headerCells.collect { "-" * Math.max(columnWidths[columnIndex++], it.size()) }.join("-|-") + "|" List props = properties.collect { it.value.iterator() } int index = 0 this.eachRow { row -> columnIndex = 0 w.print "| " + props.collect { (it.hasNext() ? it.next() : " ").padRight(columnWidths[columnIndex++]) }.join(" | ") + " | " w.println row.collect { format.format(it).padRight(columnWidth) }.join(" | ") + "|" ++index } return w } @CompileStatic private void writeObject(ObjectOutputStream oos) throws IOException { // default serialization oos.defaultWriteObject(); oos.writeObject(this.properties) } @CompileStatic private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { // default deserialization ois.defaultReadObject(); Map props = (Map)ois.readObject() props.each { Map.Entry e -> setProperty((String)e.key, e.value) } } void setColumnNames(List names) { this.names = names } void setNames(List names) { this.names = names } Object getProperty(String name) { if(name in this.@names) { return this.col(this.@names.indexOf(name)) } else { return super.getProperty(name) } } static Matrix fromListMap(List valueList) { // Get the column names and types from the first row Map row0 = valueList[0] List numerics = row0.findIndexValues { it.value instanceof Number } List nonNumerics = row0.grep { !(it.value instanceof Number) }*.key final int numNumerics = numerics.size() double [][] data = new double[valueList.size()][] valueList.eachWithIndex { Map values, int index -> data[index] = values.collect {it}[numerics].collect { it.value.toDouble() } } Matrix result = new Matrix(data) nonNumerics.each { key -> result[key] = valueList.collect { it[key]} } result.@names = row0*.key[numerics] return result } int size() { return matrix.rowDimension } } graxxia-1.0.1/src/main/groovy/graxxia/MatrixColumn.groovy000066400000000000000000000047061255110771500235630ustar00rootroot00000000000000/* * Graxxia - Groovy Maths Utililities * * Copyright (C) 2014 Simon Sadedin, ssadedingmail.com and contributors. * * This file is licensed under the Apache Software License Version 2.0. * For the avoidance of doubt, it may be alternatively licensed under GPLv2.0 * and GPLv3.0. Please see LICENSE.txt in your distribution directory for * further details. */ package graxxia; import java.util.Iterator; import groovy.transform.CompileStatic; /** * A proxy object representing a column in a matrix. *

    * The data in a {@link Matrix} is stored natively in row format. That is, * each row is stored as a native Java array of double values. This makes * accessing data by row very efficient, but doesn't give you an easy way to * pass around or treat a column of values as a collection without * first copying them to another data structure. This class wraps * an {@link Iterable} interface around a column of values without actually copying * the data. It does this keeps a reference to the underlying matrix and * implements iteration and random access (via square bracket notation) * by reflecting values into the appropriate column of the underlying * Matrix. * * @author simon.sadedin@mcri.edu.au */ class MatrixColumn implements Iterable { int columnIndex Matrix sourceMatrix MatrixColumn() { name = "C"+columnIndex } String name Object getAt(Object index) { if(index instanceof Integer) sourceMatrix.dataRef[index][columnIndex] else if(index instanceof List) sourceMatrix.dataRef[index].collect { it[columnIndex] } } @CompileStatic double getDoubleAt(int index) { return sourceMatrix.matrix.dataRef[index][columnIndex] } int size() { sourceMatrix.matrix.rowDimension } Object asType(Class c) { if(c == List) { return sourceMatrix.matrix.getColumn(columnIndex) as List } else if(c == double[]) { return sourceMatrix.matrix.getColumn(columnIndex) } else { return super.asType(c) } } Iterator iterator() { return new MatrixColumnIterator(this.sourceMatrix.matrix.dataRef, this.columnIndex) } boolean equals(Object o) { int i = 0 return (o.every { it == this[i++] }) } String toString() { "[" + this.collect {it}.join(",") + "]" } }graxxia-1.0.1/src/main/groovy/graxxia/MatrixColumnIterator.java000077500000000000000000000013041255110771500246630ustar00rootroot00000000000000package graxxia; import java.util.Iterator; public class MatrixColumnIterator implements Iterator { private final double [][] values; private final int columnIndex; private int rowIndex = 0; private final int max; public MatrixColumnIterator(double [][] values, int columnIndex) { this.values = values; this.columnIndex = columnIndex; this.max = values.length; } @Override public boolean hasNext() { return rowIndex < max; } @Override public Double next() { return values[rowIndex++][columnIndex]; } @Override public void remove() { throw new UnsupportedOperationException(); } } graxxia-1.0.1/src/main/groovy/graxxia/MatrixColumnList.groovy000066400000000000000000000004471255110771500244150ustar00rootroot00000000000000package graxxia class MatrixColumnList { @Delegate ArrayList columns Object getAt(Range arg) { Map columnMap = [columns[arg]*.name, columns[arg]].transpose().collectEntries() def result = new Matrix(columnMap) return result } } graxxia-1.0.1/src/main/groovy/graxxia/MatrixValueAdapter.groovy000066400000000000000000000043411255110771500246760ustar00rootroot00000000000000package graxxia import java.text.ParseException; import java.text.SimpleDateFormat import java.util.Date; /** * Defines an interface that can be implemented to allow integration of * user-defined data types into Matrix and TSV / CSV parsing functions * * @author ssadedin@gmail.com */ interface MatrixValueAdapter { /** * Return true if the given value is parseable as this type * * @return */ boolean sniff(Object value) /** * Encode the given object * * @param obj * @return String value of the object, for writing to file */ String serialize(T obj) /** * Parse the given object. * * @return */ T deserialize(Object obj) throws ParseException } class NumberMatrixValueAdapter implements MatrixValueAdapter { @Override public boolean sniff(Object value) { return value instanceof Number } @Override public String serialize(Double obj) { return String.valueOf(obj); } @Override public Double deserialize(Object obj) throws ParseException { return (double) obj; } } class StringMatrixValueAdapter implements MatrixValueAdapter { @Override public boolean sniff(Object value) { return true } @Override public String serialize(String obj) { return obj } @Override public String deserialize(Object obj) throws ParseException { return String.valueOf(obj) } } class DateMatrixValueAdapter implements MatrixValueAdapter { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS") @Override public boolean sniff(Object value) { if(value instanceof Date) return true if(!(value instanceof String)) return false try { format.parse(value) return true } catch (ParseException e) { return false } } @Override public String serialize(Date obj) { return format.format(obj); } @Override public Date deserialize(Object obj) throws ParseException { return format.parse(String.valueOf(obj)) } }graxxia-1.0.1/src/main/groovy/graxxia/Regression.groovy000077500000000000000000000050161255110771500232570ustar00rootroot00000000000000/* * Graxxia - Groovy Maths Utilities * * Copyright (C) 2014 Simon Sadedin, ssadedingmail.com and contributors. * * This file is licensed under the Apache Software License Version 2.0. * For the avoidance of doubt, it may be alternatively licensed under GPLv2.0 * and GPLv3.0. Please see LICENSE.txt in your distribution directory for * further details. */ package graxxia import org.apache.commons.math3.stat.regression.OLSMultipleLinearRegression; import org.codehaus.groovy.runtime.NullObject; class RegressionCategory { static RegressionVariable bitwiseNegate(RegressionVariable x) { x.model.predictors = [x] return x } static List bitwiseNegate(List values) { values[0].model.predictors = values return values } } class RegressionVariable { Regression model String name Closure operator RegressionVariable bitwiseNegate(RegressionVariable other) { model.response = this model.predictors = [other] return other } List bitwiseNegate(List variables) { model.predictors = variables return variables } List plus(RegressionVariable other) { other.operator = { arg1, arg2 -> arg1 + arg2 } [this, other] } String toString() { name } } class Regression { RegressionVariable response List predictors @Delegate OLSMultipleLinearRegression model = new OLSMultipleLinearRegression() public Regression() { } void model(Closure c) { c.delegate = this c.resolveStrategy = Closure.DELEGATE_FIRST use(RegressionCategory) { c() } } def propertyMissing(String name) { println "Creating regression variable $name" new RegressionVariable(name:name, model: this) } def methodMissing(String name, args) { this.response = new RegressionVariable(name:name) this.predictors = args[0] instanceof List ? args.flatten() : [args[0]] } void run(def response, Matrix data) { def columns = data.getColumns(predictors.collect {it .name}) def predictorData = new Matrix(columns) model.newSampleData(response as double[], predictorData.transpose().dataRef) } String toString() { "Regression of $response.name on ${predictors*.name.join(',')}" } } graxxia-1.0.1/src/main/groovy/graxxia/Stats.groovy000077500000000000000000000307341255110771500222420ustar00rootroot00000000000000/* * Graxxia - Groovy Maths Utilities * * Copyright (C) 2014 Simon Sadedin, ssadedingmail.com and contributors. * * This file is licensed under the Apache Software License Version 2.0. * For the avoidance of doubt, it may be alternatively licensed under GPLv2.0 * and GPLv3.0. Please see LICENSE.txt in your distribution directory for * further details. */ package graxxia import groovy.transform.CompileStatic; import java.text.ParseException; import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; import org.apache.commons.math3.stat.descriptive.SummaryStatistics; /** * A Groovy wrapper for Commons-Math DescriptiveStatistcs, combined * with numerous convenience methods that return SummaryStatistics. *

    * The summary static methods are used to easily create summary statistics * for various collections and iterables. A special class of methods supports efficient * creation of statistics for defined ranges of integers (eg: Coverage Depth values). The * motivation of it is to be able to calculate the median of coverage depth values efficiently * without storing the entire set in memory. See {@link IntegerStats} for more information. *

    * The most basic method takes any Iterable and turns it into a SummaryStatistics object: *

     * x = [1,4,5,6]
     * assert Stats.summary(x).mean == 4
     * 
    * As an alternative, a second method accepts a closure, which is called repeatedly * until an exception occurs such as ArrayIndexOutOfBounds, NoSuchElementException, etc. * This allows an inversion of control, and thus, effectively, a streaming model for objects * that aren't necessarily iterable: *
     * int i = 0
     * assert Stats.summary {  x[i++] }.mean == 4
     * 
    * The {@link Stats} class links well with the {@link Matrix} class to allow easy and efficient * calculation of statistics for matrix columns and rows: *
     * Matrix m = new Matrix(2,2,[1,2,3,4])
     * assert Stats.from(m[][1]).mean == 3
     * 
    * * @author simon.sadedin@mcri.edu.au */ class Stats extends DescriptiveStatistics { static final long serialVersionUID = 0L public Stats() { } public Stats(int windowSize) { super(windowSize) } /** * Convenience function to add sample value to statistics * * @param value */ void leftShift(value) { this.addValue(value) } static Stats from(double [] values) { from(values,null) } /** * A concrete implementation of {@link #from(Iterable, Closure)} specialised * for arrays of double[] values. * * @param values values to calculate statistics for * @param c Closure to filter or transform results * @return {@link Stats} object containing stastitics about the given values */ @CompileStatic static Stats from(double [] values, Closure c) { Stats s = new Stats() boolean withIndex = (c != null) && c.maximumNumberOfParameters > 1 for(int i=0; i * An optional closure can be supplied that has dual functionality: *
  • It can filter the values *
  • It can transform the values * If the result of the closure is a boolean, it is treated as a filter: *
         * x = [2,3,4,5,6]
         * assert Stats.from(x) { it % 2 == 0 }.mean == 4 // mean of 2,4,6
         * 
    * Alternatively if the value returned is numeric, it is treated as a transformation: *
         * x = [2,3,4,5,6]
         * assert Stats.from(x) { it % 2 }.mean == 1.6 // (3+5)/5
         * 
    * Of course, any {@link Iterable} could be easily transformed using standard * Groovy collection operations to achieve the same effect: *
         * x = [2,3,4,5,6]
         * assert Stats.from(x.collect { it % 2 }).mean == 1.6 // (3+5)/5
         * 
    * However the latter requires a complete copy of the transformed data be * temporarily created in memory, while the former can potentially stream * any number of values in while never consuming anything more than trivial * memory overhead. * * @param values Iterable object supplying values that can be parsed as numeric * @param c * @return */ @CompileStatic static Stats from(Iterable values, Closure c=null) { Stats s = new Stats() boolean withIndex = c != null && c.maximumNumberOfParameters > 1 values.eachWithIndex { rawValue, index -> if(c == null) { double value = (double) (rawValue instanceof Number ? rawValue : Double.parseDouble(String.valueOf(rawValue))) s.addValue(value) } else { def value = withIndex ? c(rawValue, index) : c(rawValue) if(value != false) { if(value == true) s.addValue((double)rawValue) else s.addValue(((Number)value).toDouble()) } } } return s } /** * Compute statistcs from values read from the given input stream. The * values are expected to be numeric. This is especially useful for piping * input in from standard input, eg. in Bash: *
         * cut -f 6 coverage.txt | groovy -e 'println(Stats.from())'
         * 
    * If a closure is provided then the caller can transorm the values before * they are added. If the closure returns false then the value is not included, * which gives the caller the opportunity to filter out values they might * not be interested in. * * @param values * @return */ static Stats read(InputStream values = System.in, Closure c=null) { Stats s = new Stats() values.eachLine { line -> def value = Double.parseDouble(line.trim()) if(c == null) { s.addValue(value) } else { value = c(value) if(value != false) s.addValue(value) } } return s } @CompileStatic static mean() { SummaryStatistics s = new SummaryStatistics() int errorCount = 0; System.in.eachLine { String line -> try { s.addValue(Double.parseDouble(line.trim())) } catch(ParseException e) { ++errorCount } } if(errorCount>0) System.err.println "WARNING: $errorCount lines could not be parsed as numbers" return s.mean } static Double mean(Iterable iterable) { return summary(iterable.iterator()).mean } static Double mean(Iterator i) { return summary(i).mean } static SummaryStatistics summary(Iterable iterable) { summary(iterable.iterator()) } static summary(Iterator i) { summary { i.next() } } static Double mean(double... values) { summary(values).mean } static Double mean(Closure c) { summary(c).mean } @CompileStatic static SummaryStatistics summary(double... values) { int i=0; summary { values[(int)(i++)] } } /** * Convenience method for returning the median of values read from stdin * using a default maximum value of 10,000. * * @return */ static median() { percentile().getPercentile(50) } static median(int max, Closure c) { percentile(max,c).getPercentile(50) } static median(Closure c) { percentile(10000,c).getPercentile(50) } static percentile(int max) { percentile(max, System.in) } static percentile() { percentile(10000, System.in) } /** * Return a IntegerStats object by reading lines from the * given input stream until no more lines are left. * * @param max See #percentile(max,Closure) * @param i input stream * @return a IntegerStats object */ @CompileStatic static percentile(int max, InputStream i) { i.withReader { Reader r -> percentile(10000) { r.readLine()?.trim() } } } /** * Calculate Percentile object by accepting values from * the given closure until it either: * *
  • returns null *
  • throws NoSuchElementException *
  • throws ArrayIndexOutOfBounds exception * * @param max An estimate of the maximum possible value that the percentile * values that are requested will have. If the actual value is * above this level then an exception will be thrown when the * value is requested * @param c * @return */ @CompileStatic static percentile(int max, Closure c) { IntegerStats p = new IntegerStats(max) def value = c() try { while(value != null) { if(value instanceof Integer) { p.addValue(value) } else if(value instanceof Double) { p.addValue(((Double)value).toInteger()) } else if(value instanceof Float) { p.addValue(((Float)value).toInteger()) } else if(value instanceof String) { p.addValue(((String)value).toInteger()) } else { p.addValue(String.valueOf(value).toInteger()) } value = c() } } catch(NoSuchElementException e) { // Do nothing! } catch(ArrayIndexOutOfBoundsException e) { // Do nothing! } return p } static SummaryStatistics summary(Closure c) { summary(c,null) } /** * Return a SummaryStatistics object obtained by executing the * given closure repeatedly until it either * *
  • returns null *
  • throws NoSuchElementException *
  • throws ArrayIndexOutOfBounds exception * * @param c1 the closure that returns values * @param c2 an optional closure that filters the results. * If it accepts a single parameter then only the * value is passed, if it accepts 2 parameters then * the index of the value is passed as well * * @return {@link SummaryStatistics} object */ @CompileStatic static SummaryStatistics summary(Closure c1, Closure c2) { SummaryStatistics s = new SummaryStatistics() int i=0 try { def value = c1() while(value != null) { if(c2 == null || c2(value,i)) { if(value instanceof Double) { s.addValue((Double)value) } else if(value instanceof Float) { s.addValue(((Float)value).toDouble()) } else if(value instanceof Integer) { s.addValue(((Integer)value).toDouble()) } else if(value instanceof String) { s.addValue(((String)value).toDouble()) } else { s.addValue(String.valueOf(value).toDouble()) } } value = c1() ++i } } catch(NoSuchElementException e) { // Do nothing! } catch(ArrayIndexOutOfBoundsException e) { // Do nothing! } return s } } graxxia-1.0.1/src/main/groovy/graxxia/TSV.groovy000077500000000000000000000172271255110771500216220ustar00rootroot00000000000000/* * Graxxia - Groovy Maths Utilities * * Copyright (C) 2014 Simon Sadedin, ssadedingmail.com and contributors. * * This file is licensed under the Apache Software License Version 2.0. * For the avoidance of doubt, it may be alternatively licensed under GPLv2.0 * and GPLv3.0. Please see LICENSE.txt in your distribution directory for * further details. */ package graxxia import com.xlson.groovycsv.CsvParser; import com.xlson.groovycsv.CsvIterator; import com.xlson.groovycsv.PropertyMapper; import groovy.transform.CompileStatic; import java.io.Reader; import java.util.zip.GZIPInputStream /** * A convenience wrapper around Groovy-CSV (which is * itself a convenience wrapper around OpenCSV). *

    * The main functionality added is auto-conversion of column types. * When the first line is read, the data is "sniffed" and the type is * inferred in the order that each column can be parsed as: *

  • Double *
  • Integer *
  • Boolean *
  • String (default) *

    * The parsed lines are then converted to these data types where possible * (where not possible, they just leave the value as Strings). * *

    Example

    *
     * // File is : foo\tbar\t10\t4.2
     * for(line in new TSV("test.tsv")) {
     *    println line[2] + line[3] // Works as Integer and Float
     * }
     * 
    * Passing column names is simple: *
     * for(line in new TSV("test.tsv", columnNames:['name','something','age','height'])) {
     *   println line.height + line.age
     * }
     * 
    * * @author Simon */ class TSV implements Iterable { CsvIterator parser Closure reader Map options static List formats = [ new DateMatrixValueAdapter()] TSV(Map options=[:], String fileName) { this.reader = { getReader(fileName) } this.options = options checkFirstLine() } TSV(Map options=[:], Reader r) { this.reader = { return r } this.options = options checkFirstLine() } void checkFirstLine() { if(this.options.containsKey('columnNames') && !this.options.containsKey('readFirstLine')) { this.options.readFirstLine = true } } CsvIterator newIterator() { if(!options.containsKey("separator")) options.separator = "\t" CsvParser.parseCsv(options, reader()) } /* TSV(String fileName, List columnNames = null) { // If the first line starts with # then we treat it as column headers if(!new File(fileName).withReader { r -> String firstLine = r.readLine() if(firstLine.startsWith('#')) { def cols = firstLine.substring(1).trim().split("\t") parser = CsvParser.parseCsv(columnNames: cols, separator: '\t', readFirstLine: true, r) return true } return false }) { new File(fileName).withReader { r -> if(columnNames) parser = CsvParser.parseCsv(r, columns: columnNames, separator:'\t') else parser = CsvParser.parseCsv(r, separator:'\t') } } } */ // TSV(Reader reader) { // String line = reader.readLine() // parser = CsvParser.parseCsv(reader) // } // TSV(Reader reader, List columnNames) { parser = CsvParser.parseCsv(reader, columnNames: columnNames, readFirstLine: true, separator: '\t') } Iterator iterator() { Iterator i = newIterator() List columnTypes = options.columnTypes new Iterator() { boolean hasNext() { i.hasNext() } Object next() { def line = i.next() if(!columnTypes) { columnTypes = [String] * line.values.size() line.values.eachWithIndex { v, index -> if(v.isInteger()) columnTypes[index] = Integer else if(v.isDouble()) columnTypes[index] = Double else if(v in ["true","false"]) columnTypes[index] = Boolean else { for(f in formats) { if(f.sniff(v)) { columnTypes[index] = f } } } } } line.values = TSV.this.convertColumns(line.values, columnTypes) return line } void remove() { throw new UnsupportedOperationException() } } } @CompileStatic List convertColumns(String [] values, List columnTypes) { List newValues = values as List final int numColumns = columnTypes.size() for(int index = 0; index return r.readLine().split(separator).size() } } static getReader(String fileName) { fileName.endsWith(".gz") ? new GZIPInputStream(new FileInputStream(fileName)).newReader() : new File(fileName).newReader() } } class CSV extends TSV { CSV(Map options=[:], String fileName) { super(options + [separator:','],fileName) } CSV(Map options=[:], Reader r) { super(options + [separator:','],r) } } graxxia-1.0.1/src/test/000077500000000000000000000000001255110771500147265ustar00rootroot00000000000000graxxia-1.0.1/src/test/groovy/000077500000000000000000000000001255110771500162535ustar00rootroot00000000000000graxxia-1.0.1/src/test/groovy/IntegerStatsTest.groovy000066400000000000000000000021361255110771500230000ustar00rootroot00000000000000import static org.junit.Assert.*; import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics import org.junit.Test; import graxxia.* class IntegerStatsTest { @Test public void testCoveragePercentile() { def tests = [ [2,5,2,3,2,1,4,3,2,2,2], [20] * 10 + [30]*15, [20] * 15 + [30]*10, [20] * 10 + [30]*10 ] for(values in tests) { def c = new IntegerStats(1000) def d = new DescriptiveStatistics() values.each { c.addValue(it); d.addValue(it) } println c.getPercentile(50) println d.getPercentile(50).toInteger() assert c.getPercentile(50) == d.getPercentile(50).toInteger() } } @Test void testBreakTie() { def values = [20] * 10 + [30]*10 def c = new IntegerStats(1000) def d = new DescriptiveStatistics() values.each { c.addValue(it); d.addValue(it) } assert c.getPercentile(50) == d.getPercentile(50).toInteger() } } graxxia-1.0.1/src/test/groovy/MatrixTest.groovy000066400000000000000000000246771255110771500216460ustar00rootroot00000000000000import static org.junit.Assert.*; import org.junit.Test; import graxxia.* import groovy.time.TimeCategory; class MatrixTest { @Test public void test() { def m = new Matrix(3,4) m[2][3] = 9 assert m[2][3] == 9 } @Test void testColumn() { def m = new Matrix(4,2, [0d,1d, 2d,3d, 4d,5d, 6d,6d]) assert m.col(0)[0] == 0d assert m.col(0)[1] == 2d println m.col(0).grep { println(it); it > 0 } println m.col(0).find { it > 0 } println m.col(0).findIndexValues { it > 0 } println m.col(0)[0,2] println m.col(1).collect { it %2 } } @Test void testColumnMean() { def m = new Matrix(4,2, [0d,1d, 2d,3d, 4d,5d, 6d,6d]) assert Stats.mean(m.col(0)) == 3 assert Stats.mean(m.col(1)) == 15 / 4 assert Stats.mean(m[][0]) == 3 } @Test void testColumnAccess() { def m = new Matrix(4,3, [0d,1d,3d, 2d,3d,4d, 4d,5d,6d, 6d,6d,6d]) assert m.columns.size() == 3 println "m[][2] = " + m.columns[2] println "m[][2] = " + m[][2] println "m[][2][3] = " + m[][2][3] assert m[][2][3] == 6d } @Test void bigMatrix() { Matrix m = new Matrix(60,2) for(int i=0; i row[0]>3 } == [1] } // @Test void testPerformance() { double [][] values Random r = new Random() int size = 10000 Matrix m int count = 0 // Recorded at about 651 ms 19/5/2014 assert Utils.time("Initialize") { m = new Matrix(size,size).transform { ++count; r.nextGaussian() } } < 30000 assert count == size*size // Recorded at about 263 ms 19/5/2014 assert Utils.time("transform") { Matrix n = m.transform { x -> x * 2 } } < 20000 assert Utils.time("mean by column") { int i = 0 def means = m.columns.collect { Stats.mean(it) } println "First 10 means are: " + means[0..10] } < 50000 } @Test void testNonNumericColumns() { Matrix m = new Matrix([[2,5,3,3,4], [5,6,7,2,4]]) m.@names = ["legs","toes","feet","ears","eyes"] m.animal = ["frog","dog"] // m.grep { animal == "frog" && legs > 2 } assert m.grep { animal == "frog" }.rowDimension == 1 Matrix n = m.transformRows { if(animal == "dog") [1d,1d] else [2d,2d] } assert n[0][0] == 2 assert n[0][1] == 2 assert n[1][0] == 1 assert n[1][1] == 1 assert m.which { animal == "frog" } == [0] def animals = [] m.eachRow { animals.add(animal) } assert animals == ["frog", "dog"] assert m.grep { legs > 3 }.animal == ["dog"] } @Test void testSaveNonNumeric() { Matrix m = new Matrix([[2,5,3,3,4], [5,6,7,2,4]]) m.@names = ["legs","toes","feet","ears","eyes"] m.animal = ["frog","dog"] m.ages = [2,7] m.save("testSaveNonNumeric.tsv") println new File("testSaveNonNumeric.tsv").text Matrix m2 = Matrix.load("testSaveNonNumeric.tsv") println m2.toString() println m2.legs assert m2.legs.find { Math.abs(it - 2) < 0.01 } assert m2.legs.find { Math.abs(it - 5) < 0.01 } assert m2.animal == ["frog","dog"] assert "legs" in m2.@names assert "eyes" in m2.@names new File("testSaveNonNumeric.tsv").delete() } @Test void testSaveCustomType() { Matrix m = new Matrix([[2,5,3], [5,6,7]]) m.@names = ["legs","toes","feet"] m.date = [new Date(),new Date(System.currentTimeMillis()-24*3600*1000)] m.save("testSaveDate.tsv") println new File("testSaveDate.tsv").text Matrix m2 = Matrix.load("testSaveDate.tsv") println m2 assert m2.date[0] instanceof Date } @Test void testInitFromMap() { Matrix m = new Matrix( frog: [2,4,5], tree: [8,2,7] ) assert m[2][1] == 7 assert m.frog[1] == 4 } @Test void testMarkdown() { Matrix m = new Matrix( frog: [2,4,5], tree: [8,2,7] ) m.cow = ["maisy is a really good cow. I think she is great","daisy","doo"] StringWriter sw = new StringWriter() m.toMarkdown(sw) println sw.toString() } @Test void testSerialize() { Matrix m = new Matrix( frog: [2,4,5], tree: [8,2,7] ) m.cow = ["maisy","daisy","doo"] println m[1][0] ByteArrayOutputStream baos def oos = new ObjectOutputStream(baos=new ByteArrayOutputStream()) oos.writeObject(m) oos.close() new ByteArrayInputStream(baos.toByteArray()).withObjectInputStream { ois -> Matrix m2 = ois.readObject() assert m2.cow[2] == "doo" assert m2[1][0] == 4 } } @Test void testGroupBy() { Matrix m = new Matrix( [ [2,4], // brown [8,2], // white [4,5], // white [3,6], // brown [7,2] // black ] ) m.@names = ["age","weight"] m.color = ["brown","white","white","brown", "black"] def grouped = m.groupBy { color } assert grouped.brown[0][0] == 2 assert grouped.brown[1][0] == 3 assert grouped.brown[1][1] == 6 assert grouped.black.rowDimension == 1 assert grouped.black.age == [7] } @Test void testCountBy() { Matrix m = new Matrix( [ [2,4], // brown [8,2], // white [4,5], // white [3,6], // brown [7,2] // black ] ) m.@names = ["age","weight"] m.color = ["brown","white","white","brown", "black"] def counts = m.countBy { color } assert counts.white == 2 assert counts.brown == 2 assert counts.black == 1 } @Test void testColumnSubset() { def m = new Matrix(x1: [1,2,3,4,5], x2: [2,4,6,8,10], x3:[7,6,5,4,3]) def m2c = m[] assert m2c.getClass().name == "graxxia.MatrixColumnList" def m2 = m[][1..-1] assert m2[0][0] == 2 assert m2.@names == ["x2","x3"] println "Matrix after columns subset = " + m2 } @Test void testCollect() { def m = new Matrix(x1: [1,2,3,4,5], x2: [2,4,6,8,10], x3:[7,6,5,4,3]) assert m.collect { x1 * x2 } == [2.0d, 8.0d, 18.0d, 32.0d, 50.0d] } @Test void testFromListMap() { def lm = [ [foo: 1, bar:2, dog: "fido"], [foo: 8, bar:1, dog: "biffo"], [foo: 3, bar:9, dog: "pup"], ] Matrix m = Matrix.fromListMap(lm) println m assert m.foo[0] == 1 assert m.bar[1] == 1 assert m.dog[2] == "pup" } } graxxia-1.0.1/src/test/groovy/StatsTest.groovy000066400000000000000000000034751255110771500214710ustar00rootroot00000000000000import static org.junit.Assert.*; import org.junit.Test; import graxxia.* class StatsTest { def x = new Stats() @Test public void testConversion() { x << 4 x << 7 x << 8 println x.mean assertEquals(6.333,x.mean, 0.05) def s = Stats.from(["cat","dog","treehouse","farm"]) { it.size() } println s assertEquals 4.75, s.mean, 0.05 assertEquals 2.87, s.standardDeviation, 0.05 println Stats.from([5,4,2,9]) assertEquals 5.0, Stats.from([5,4,2,9]).mean, 0.05 } @Test void testMean() { def values = [1, 5, 7, 8] Iterator i = values.iterator() def m = Stats.mean { i.next() } println m assertEquals 5.25, m, 0.05 println Stats.mean(values) println Stats.mean(values.iterator()) assertEquals 5.25d, Stats.mean(values.iterator()), 0.05d } @Test void testIterable() { Iterable x = new Iterable() { int count = 0 def values = [5,4,3,2] as double[] Iterator iterator() { return [ hasNext : { count < values.size() }, next : { values[count++] } ] as Iterator } } // def s = Stats.from(x) // assert s.mean == 3.5 // Filter the values assert Stats.from(x) { it > 3 }.mean == 4.5 } @Test void testMidPercentile() { def values = [1, 5, 7, 8] Iterator i = values.iterator() def p50 = Stats.percentile(200) { i.next() }.getPercentile(50) assertEquals 6f, p50, 0.05 } }