diff --git a/config/datatypes_conf.xml.sample b/config/datatypes_conf.xml.sample
index 6582e3b0d05..88461ffc605 100644
--- a/config/datatypes_conf.xml.sample
+++ b/config/datatypes_conf.xml.sample
@@ -315,6 +315,7 @@
+
diff --git a/config/plugins/visualizations/charts/config/charts.xml b/config/plugins/visualizations/charts/config/charts.xml
index b3624fa1bf4..8356fc32d5f 100644
--- a/config/plugins/visualizations/charts/config/charts.xml
+++ b/config/plugins/visualizations/charts/config/charts.xml
@@ -5,6 +5,7 @@
HistoryDatasetAssociation
tabular.Tabular
+ tabular.CSV
dataset_id
diff --git a/config/plugins/visualizations/scatterplot/config/scatterplot.xml b/config/plugins/visualizations/scatterplot/config/scatterplot.xml
index 14004ceb1e5..d2649185929 100644
--- a/config/plugins/visualizations/scatterplot/config/scatterplot.xml
+++ b/config/plugins/visualizations/scatterplot/config/scatterplot.xml
@@ -5,6 +5,7 @@
HistoryDatasetAssociation
tabular.Tabular
+ tabular.CSV
dataset_id
diff --git a/lib/galaxy/datatypes/registry.py b/lib/galaxy/datatypes/registry.py
index 4d540709735..4149b893a7c 100644
--- a/lib/galaxy/datatypes/registry.py
+++ b/lib/galaxy/datatypes/registry.py
@@ -695,6 +695,7 @@ class Registry( object ):
'scf' : binary.Scf(),
'sff' : binary.Sff(),
'tabular' : tabular.Tabular(),
+ 'csv' : tabular.CSV(),
'taxonomy' : tabular.Taxonomy(),
'txt' : data.Text(),
'wig' : interval.Wiggle(),
@@ -727,6 +728,7 @@ class Registry( object ):
'scf' : 'application/octet-stream',
'sff' : 'application/octet-stream',
'tabular' : 'text/plain',
+ 'csv' : 'text/plain',
'taxonomy' : 'text/plain',
'txt' : 'text/plain',
'wig' : 'text/plain',
@@ -761,7 +763,8 @@ class Registry( object ):
tabular.Pileup(),
interval.Interval(),
tabular.Sam(),
- tabular.Eland()
+ tabular.Eland(),
+ tabular.CSV()
]
def get_converters_by_datatype( self, ext ):
diff --git a/lib/galaxy/datatypes/tabular.py b/lib/galaxy/datatypes/tabular.py
index 39949091d34..6cbc2ba0ade 100644
--- a/lib/galaxy/datatypes/tabular.py
+++ b/lib/galaxy/datatypes/tabular.py
@@ -7,6 +7,7 @@ pkg_resources.require( "bx-python" )
import gzip
import logging
import os
+import csv
from cgi import escape
from galaxy import util
from galaxy.datatypes import data
@@ -19,18 +20,211 @@ import dataproviders
log = logging.getLogger(__name__)
+
@dataproviders.decorators.has_dataproviders
-class Tabular( data.Text ):
- """Tab delimited data"""
+class TabularData( data.Text ):
+ """Generic tabular data"""
edam_format = "format_3475"
# All tabular data is chunkable.
CHUNKABLE = True
"""Add metadata elements"""
MetadataElement( name="comment_lines", default=0, desc="Number of comment lines", readonly=False, optional=True, no_value=0 )
+ MetadataElement( name="data_lines", default=0, desc="Number of data lines", readonly=True, visible=False, optional=True, no_value=0 )
MetadataElement( name="columns", default=0, desc="Number of columns", readonly=True, visible=False, no_value=0 )
MetadataElement( name="column_types", default=[], desc="Column types", param=metadata.ColumnTypesParameter, readonly=True, visible=False, no_value=[] )
MetadataElement( name="column_names", default=[], desc="Column names", readonly=True, visible=False, optional=True, no_value=[] )
+ MetadataElement( name="delimiter", default='\t', desc="Data delimiter", readonly=True, visible=False, optional=True, no_value=[] )
+
+ def set_peek( self, dataset, line_count=None, is_multi_byte=False):
+ super(TabularData, self).set_peek( dataset, line_count=line_count, is_multi_byte=is_multi_byte)
+ if dataset.metadata.comment_lines:
+ dataset.blurb = "%s, %s comments" % ( dataset.blurb, util.commaify( str( dataset.metadata.comment_lines ) ) )
+
+ def displayable( self, dataset ):
+ try:
+ return dataset.has_data() \
+ and dataset.state == dataset.states.OK \
+ and dataset.metadata.columns > 0 \
+ and dataset.metadata.data_lines != 0
+ except:
+ return False
+
+ def get_chunk(self, trans, dataset, chunk):
+ ck_index = int(chunk)
+ f = open(dataset.file_name)
+ f.seek(ck_index * trans.app.config.display_chunk_size)
+ # If we aren't at the start of the file, seek to next newline. Do this better eventually.
+ if f.tell() != 0:
+ cursor = f.read(1)
+ while cursor and cursor != '\n':
+ cursor = f.read(1)
+ ck_data = f.read(trans.app.config.display_chunk_size)
+ cursor = f.read(1)
+ while cursor and ck_data[-1] != '\n':
+ ck_data += cursor
+ cursor = f.read(1)
+ return dumps( { 'ck_data': util.unicodify( ck_data ), 'ck_index': ck_index + 1 } )
+
+ def display_data(self, trans, dataset, preview=False, filename=None, to_ext=None, chunk=None, **kwd):
+ preview = util.string_as_bool( preview )
+ if chunk:
+ return self.get_chunk(trans, dataset, chunk)
+ elif to_ext or not preview:
+ to_ext = to_ext or dataset.extension
+ return self._serve_raw(trans, dataset, to_ext)
+ elif dataset.metadata.columns > 50:
+ #Fancy tabular display is only suitable for datasets without an incredibly large number of columns.
+ #We should add a new datatype 'matrix', with its own draw method, suitable for this kind of data.
+ #For now, default to the old behavior, ugly as it is. Remove this after adding 'matrix'.
+ max_peek_size = 1000000 # 1 MB
+ if os.stat( dataset.file_name ).st_size < max_peek_size:
+ return open( dataset.file_name )
+ else:
+ trans.response.set_content_type( "text/html" )
+ return trans.stream_template_mako( "/dataset/large_file.mako",
+ truncated_data = open( dataset.file_name ).read(max_peek_size),
+ data = dataset)
+ else:
+ column_names = 'null'
+ if dataset.metadata.column_names:
+ column_names = dataset.metadata.column_names
+ elif hasattr(dataset.datatype, 'column_names'):
+ column_names = dataset.datatype.column_names
+ column_types = dataset.metadata.column_types
+ if not column_types:
+ column_types = []
+ column_number = dataset.metadata.columns
+ if column_number is None:
+ column_number = 'null'
+ return trans.fill_template( "/dataset/tabular_chunked.mako",
+ dataset = dataset,
+ chunk = self.get_chunk(trans, dataset, 0),
+ column_number = column_number,
+ column_names = column_names,
+ column_types = column_types )
+
+ def make_html_table( self, dataset, **kwargs ):
+ """Create HTML table, used for displaying peek"""
+ out = ['
']
+ try:
+ out.append( self.make_html_peek_header( dataset, **kwargs ) )
+ out.append( self.make_html_peek_rows( dataset, **kwargs ) )
+ out.append( '
' )
+ out = "".join( out )
+ except Exception, exc:
+ out = "Can't create peek %s" % str( exc )
+ return out
+
+ def make_html_peek_header( self, dataset, skipchars=None, column_names=None, column_number_format='%s', column_parameter_alias=None, **kwargs ):
+ if skipchars is None:
+ skipchars = []
+ if column_names is None:
+ column_names = []
+ if column_parameter_alias is None:
+ column_parameter_alias = {}
+ out = []
+ try:
+ if not column_names and dataset.metadata.column_names:
+ column_names = dataset.metadata.column_names
+
+ columns = dataset.metadata.columns
+ if columns is None:
+ columns = dataset.metadata.spec.columns.no_value
+ column_headers = [None] * columns
+
+ # fill in empty headers with data from column_names
+ for i in range( min( columns, len( column_names ) ) ):
+ if column_headers[i] is None and column_names[i] is not None:
+ column_headers[i] = column_names[i]
+
+ # fill in empty headers from ColumnParameters set in the metadata
+ for name, spec in dataset.metadata.spec.items():
+ if isinstance( spec.param, metadata.ColumnParameter ):
+ try:
+ i = int( getattr( dataset.metadata, name ) ) - 1
+ except:
+ i = -1
+ if 0 <= i < columns and column_headers[i] is None:
+ column_headers[i] = column_parameter_alias.get(name, name)
+
+ out.append( '' )
+ for i, header in enumerate( column_headers ):
+ out.append( '| ' )
+ if header is None:
+ out.append( column_number_format % str( i + 1 ) )
+ else:
+ out.append( '%s.%s' % ( str( i + 1 ), escape( header ) ) )
+ out.append( ' | ' )
+ out.append( '
' )
+ except Exception, exc:
+ log.exception( 'make_html_peek_header failed on HDA %s' % dataset.id )
+ raise Exception, "Can't create peek header %s" % str( exc )
+ return "".join( out )
+
+ def make_html_peek_rows( self, dataset, skipchars=None, **kwargs ):
+ if skipchars is None:
+ skipchars = []
+ out = []
+ try:
+ if not dataset.peek:
+ dataset.set_peek()
+ columns = dataset.metadata.columns
+ if columns is None:
+ columns = dataset.metadata.spec.columns.no_value
+ for line in dataset.peek.splitlines():
+ if line.startswith( tuple( skipchars ) ):
+ out.append( '| %s |
' % escape( line ) )
+ elif line:
+ elems = line.split( dataset.metadata.delimiter )
+ # we may have an invalid comment line or invalid data
+ if len( elems ) != columns:
+ out.append( '| %s |
' % escape( line ) )
+ else:
+ out.append( '' )
+ for elem in elems:
+ out.append( '| %s | ' % escape( elem ) )
+ out.append( '
' )
+ except Exception, exc:
+ log.exception( 'make_html_peek_rows failed on HDA %s' % dataset.id )
+ raise Exception, "Can't create peek rows %s" % str( exc )
+ return "".join( out )
+
+ def display_peek( self, dataset ):
+ """Returns formatted html of peek"""
+ return self.make_html_table( dataset )
+
+ # ------------- Dataproviders
+ @dataproviders.decorators.dataprovider_factory( 'column', dataproviders.column.ColumnarDataProvider.settings )
+ def column_dataprovider( self, dataset, **settings ):
+ """Uses column settings that are passed in"""
+ dataset_source = dataproviders.dataset.DatasetDataProvider( dataset )
+ delimiter = dataset.metadata.delimiter
+ return dataproviders.column.ColumnarDataProvider( dataset_source, deliminator = delimiter, **settings )
+
+ @dataproviders.decorators.dataprovider_factory( 'dataset-column',
+ dataproviders.column.ColumnarDataProvider.settings )
+ def dataset_column_dataprovider( self, dataset, **settings ):
+ """Attempts to get column settings from dataset.metadata"""
+ delimiter = dataset.metadata.delimiter
+ return dataproviders.dataset.DatasetColumnarDataProvider( dataset, deliminator = delimiter, **settings )
+
+ @dataproviders.decorators.dataprovider_factory( 'dict', dataproviders.column.DictDataProvider.settings )
+ def dict_dataprovider( self, dataset, **settings ):
+ """Uses column settings that are passed in"""
+ dataset_source = dataproviders.dataset.DatasetDataProvider( dataset )
+ delimiter = dataset.metadata.delimiter
+ return dataproviders.column.DictDataProvider( dataset_source, deliminator = delimiter, **settings )
+
+ @dataproviders.decorators.dataprovider_factory( 'dataset-dict', dataproviders.column.DictDataProvider.settings )
+ def dataset_dict_dataprovider( self, dataset, **settings ):
+ """Attempts to get column settings from dataset.metadata"""
+ delimiter = dataset.metadata.delimiter
+ return dataproviders.dataset.DatasetDictDataProvider( dataset, deliminator = delimiter, **settings )
+
+@dataproviders.decorators.has_dataproviders
+class Tabular( TabularData ):
+ """Tab delimited data"""
def set_meta( self, dataset, overwrite = True, skip = None, max_data_lines = 100000, max_guess_type_data_lines = None, **kwd ):
"""
@@ -172,191 +366,13 @@ class Tabular( data.Text ):
dataset.metadata.comment_lines = comment_lines
dataset.metadata.column_types = column_types
dataset.metadata.columns = len( column_types )
- def make_html_table( self, dataset, **kwargs ):
- """Create HTML table, used for displaying peek"""
- out = ['']
- try:
- out.append( self.make_html_peek_header( dataset, **kwargs ) )
- out.append( self.make_html_peek_rows( dataset, **kwargs ) )
- out.append( '
' )
- out = "".join( out )
- except Exception, exc:
- out = "Can't create peek %s" % str( exc )
- return out
+ dataset.metadata.delimiter = '\t'
- def make_html_peek_header( self, dataset, skipchars=None, column_names=None, column_number_format='%s', column_parameter_alias=None, **kwargs ):
- if skipchars is None:
- skipchars = []
- if column_names is None:
- column_names = []
- if column_parameter_alias is None:
- column_parameter_alias = {}
- out = []
- try:
- if not column_names and dataset.metadata.column_names:
- column_names = dataset.metadata.column_names
-
- columns = dataset.metadata.columns
- if columns is None:
- columns = dataset.metadata.spec.columns.no_value
- column_headers = [None] * columns
-
- # fill in empty headers with data from column_names
- for i in range( min( columns, len( column_names ) ) ):
- if column_headers[i] is None and column_names[i] is not None:
- column_headers[i] = column_names[i]
-
- # fill in empty headers from ColumnParameters set in the metadata
- for name, spec in dataset.metadata.spec.items():
- if isinstance( spec.param, metadata.ColumnParameter ):
- try:
- i = int( getattr( dataset.metadata, name ) ) - 1
- except:
- i = -1
- if 0 <= i < columns and column_headers[i] is None:
- column_headers[i] = column_parameter_alias.get(name, name)
-
- out.append( '' )
- for i, header in enumerate( column_headers ):
- out.append( '| ' )
- if header is None:
- out.append( column_number_format % str( i + 1 ) )
- else:
- out.append( '%s.%s' % ( str( i + 1 ), escape( header ) ) )
- out.append( ' | ' )
- out.append( '
' )
- except Exception, exc:
- log.exception( 'make_html_peek_header failed on HDA %s' % dataset.id )
- raise Exception, "Can't create peek header %s" % str( exc )
- return "".join( out )
-
- def make_html_peek_rows( self, dataset, skipchars=None, **kwargs ):
- if skipchars is None:
- skipchars = []
- out = []
- try:
- if not dataset.peek:
- dataset.set_peek()
- columns = dataset.metadata.columns
- if columns is None:
- columns = dataset.metadata.spec.columns.no_value
- for line in dataset.peek.splitlines():
- if line.startswith( tuple( skipchars ) ):
- out.append( '| %s |
' % escape( line ) )
- elif line:
- elems = line.split( '\t' )
- # we may have an invalid comment line or invalid data
- if len( elems ) != columns:
- out.append( '| %s |
' % escape( line ) )
- else:
- out.append( '' )
- for elem in elems:
- out.append( '| %s | ' % escape( elem ) )
- out.append( '
' )
- except Exception, exc:
- log.exception( 'make_html_peek_rows failed on HDA %s' % dataset.id )
- raise Exception, "Can't create peek rows %s" % str( exc )
- return "".join( out )
-
- def get_chunk(self, trans, dataset, chunk):
- ck_index = int(chunk)
- f = open(dataset.file_name)
- f.seek(ck_index * trans.app.config.display_chunk_size)
- # If we aren't at the start of the file, seek to next newline. Do this better eventually.
- if f.tell() != 0:
- cursor = f.read(1)
- while cursor and cursor != '\n':
- cursor = f.read(1)
- ck_data = f.read(trans.app.config.display_chunk_size)
- cursor = f.read(1)
- while cursor and ck_data[-1] != '\n':
- ck_data += cursor
- cursor = f.read(1)
- return dumps( { 'ck_data': util.unicodify( ck_data ), 'ck_index': ck_index + 1 } )
-
- def display_data(self, trans, dataset, preview=False, filename=None, to_ext=None, chunk=None, **kwd):
- preview = util.string_as_bool( preview )
- if chunk:
- return self.get_chunk(trans, dataset, chunk)
- elif to_ext or not preview:
- to_ext = to_ext or dataset.extension
- return self._serve_raw(trans, dataset, to_ext)
- elif dataset.metadata.columns > 50:
- #Fancy tabular display is only suitable for datasets without an incredibly large number of columns.
- #We should add a new datatype 'matrix', with its own draw method, suitable for this kind of data.
- #For now, default to the old behavior, ugly as it is. Remove this after adding 'matrix'.
- max_peek_size = 1000000 # 1 MB
- if os.stat( dataset.file_name ).st_size < max_peek_size:
- return open( dataset.file_name )
- else:
- trans.response.set_content_type( "text/html" )
- return trans.stream_template_mako( "/dataset/large_file.mako",
- truncated_data = open( dataset.file_name ).read(max_peek_size),
- data = dataset)
- else:
- column_names = 'null'
- if dataset.metadata.column_names:
- column_names = dataset.metadata.column_names
- elif hasattr(dataset.datatype, 'column_names'):
- column_names = dataset.datatype.column_names
- column_types = dataset.metadata.column_types
- if not column_types:
- column_types = []
- column_number = dataset.metadata.columns
- if column_number is None:
- column_number = 'null'
- return trans.fill_template( "/dataset/tabular_chunked.mako",
- dataset = dataset,
- chunk = self.get_chunk(trans, dataset, 0),
- column_number = column_number,
- column_names = column_names,
- column_types = column_types )
-
- def set_peek( self, dataset, line_count=None, is_multi_byte=False):
- super(Tabular, self).set_peek( dataset, line_count=line_count, is_multi_byte=is_multi_byte)
- if dataset.metadata.comment_lines:
- dataset.blurb = "%s, %s comments" % ( dataset.blurb, util.commaify( str( dataset.metadata.comment_lines ) ) )
- def display_peek( self, dataset ):
- """Returns formatted html of peek"""
- return self.make_html_table( dataset )
- def displayable( self, dataset ):
- try:
- return dataset.has_data() \
- and dataset.state == dataset.states.OK \
- and dataset.metadata.columns > 0 \
- and dataset.metadata.data_lines != 0
- except:
- return False
def as_gbrowse_display_file( self, dataset, **kwd ):
return open( dataset.file_name )
def as_ucsc_display_file( self, dataset, **kwd ):
return open( dataset.file_name )
- # ------------- Dataproviders
- @dataproviders.decorators.dataprovider_factory( 'column', dataproviders.column.ColumnarDataProvider.settings )
- def column_dataprovider( self, dataset, **settings ):
- """Uses column settings that are passed in"""
- dataset_source = dataproviders.dataset.DatasetDataProvider( dataset )
- return dataproviders.column.ColumnarDataProvider( dataset_source, **settings )
-
- @dataproviders.decorators.dataprovider_factory( 'dataset-column',
- dataproviders.column.ColumnarDataProvider.settings )
- def dataset_column_dataprovider( self, dataset, **settings ):
- """Attempts to get column settings from dataset.metadata"""
- return dataproviders.dataset.DatasetColumnarDataProvider( dataset, **settings )
-
- @dataproviders.decorators.dataprovider_factory( 'dict', dataproviders.column.DictDataProvider.settings )
- def dict_dataprovider( self, dataset, **settings ):
- """Uses column settings that are passed in"""
- dataset_source = dataproviders.dataset.DatasetDataProvider( dataset )
- return dataproviders.column.DictDataProvider( dataset_source, **settings )
-
- @dataproviders.decorators.dataprovider_factory( 'dataset-dict', dataproviders.column.DictDataProvider.settings )
- def dataset_dict_dataprovider( self, dataset, **settings ):
- """Attempts to get column settings from dataset.metadata"""
- return dataproviders.dataset.DatasetDictDataProvider( dataset, **settings )
-
-
class Taxonomy( Tabular ):
def __init__(self, **kwd):
"""Initialize taxonomy datatype"""
@@ -820,3 +836,73 @@ class FeatureLocationIndex( Tabular ):
MetadataElement( name="columns", default=2, desc="Number of columns", readonly=True, visible=False )
MetadataElement( name="column_types", default=['str', 'str'], param=metadata.ColumnTypesParameter, desc="Column types", readonly=True, visible=False, no_value=[] )
+@dataproviders.decorators.has_dataproviders
+class CSV( TabularData ):
+ """
+ Delimiter-separated table data.
+ This includes CSV, TSV and other dialects understood by the
+ Python 'csv' module https://docs.python.org/2/library/csv.html
+ """
+ delimiter = ','
+ file_ext = 'csv' # File extension
+ peek_size = 1024 # File chunk used for sniffing CSV dialect
+
+ def is_int( self, column_text ):
+ try:
+ int( column_text )
+ return True
+ except:
+ return False
+
+ def is_float( self, column_text ):
+ try:
+ float( column_text )
+ return True
+ except:
+ if column_text.strip().lower() == 'na':
+ return True #na is special cased to be a float
+ return False
+
+ def guess_type( self, text ):
+ if self.is_int(text): return 'int'
+ if self.is_float(text): return 'float'
+ else: return 'str'
+
+ def sniff( self, filename ):
+ """ Return True if if recognizes dialect and header. """
+ if not csv.Sniffer().has_header(open(filename, 'r').read(self.peek_size)):
+ return False
+ # Fetch at least three consecutive lines to be reasonably sure
+ reader = csv.reader(open(filename, 'r'))
+ for i in range(0, 3):
+ reader.next()
+ return True
+
+ def set_meta( self, dataset, **kwd ):
+ with open(dataset.file_name, 'r') as csvfile:
+ # Parse file
+ reader = csv.reader(csvfile)
+ data_row = None
+ header_row = None
+ try:
+ header_row = reader.next()
+ data_row = reader.next()
+ for row in reader:
+ pass
+ except csv.Error as e:
+ raise ('line %d: %s' % (reader.line_num, e))
+
+ # Guess column types
+ if len(header_row) != len(data_row):
+ raise ('mismatching number of columns in header and data')
+ column_types = []
+ for cell in data_row:
+ column_types.append(self.guess_type(cell))
+
+ # Set metadata
+ dataset.metadata.data_lines = reader.line_num - 1
+ dataset.metadata.comment_lines = 1
+ dataset.metadata.column_types = column_types
+ dataset.metadata.columns = len(header_row)
+ dataset.metadata.column_names = header_row
+ dataset.metadata.delimiter = reader.dialect.delimiter
diff --git a/tools/data_source/upload.xml b/tools/data_source/upload.xml
index d65ef7ca49c..196237fd414 100644
--- a/tools/data_source/upload.xml
+++ b/tools/data_source/upload.xml
@@ -213,6 +213,12 @@ Any data in tab delimited format (tabular)
-----
+**Table (delimiter-separated)**
+
+Any delimiter-separated tabular data (CSV or TSV).
+
+-----
+
**Wig**
The wiggle format is line-oriented. Wiggle data is preceded by a track definition line, which adds a number of options for controlling the default display of this track.
diff --git a/tools/stats/gsummary.py b/tools/stats/gsummary.py
index 334dda976d7..10b22a78f11 100755
--- a/tools/stats/gsummary.py
+++ b/tools/stats/gsummary.py
@@ -1,7 +1,12 @@
#!/usr/bin/env python
import sys, re, tempfile
-from rpy import *
+try:
+ from rpy2.rpy_classic import *
+except:
+ # RPy isn't maintained, and doesn't work with R>3.0, use it as a fallback
+ from rpy import *
+
# Older py compatibility
try:
set()
@@ -87,11 +92,11 @@ def main():
stop_err( "Invalid column or column data values invalid for computation. See tool tips and syntax for data requirements." )
else:
# summary function and return labels
+ set_default_mode( NO_CONVERSION )
summary_func = r( "function( x ) { c( sum=sum( as.numeric( x ), na.rm=T ), mean=mean( as.numeric( x ), na.rm=T ), stdev=sd( as.numeric( x ), na.rm=T ), quantile( as.numeric( x ), na.rm=TRUE ) ) }" )
headings = [ 'sum', 'mean', 'stdev', '0%', '25%', '50%', '75%', '100%' ]
headings_str = "\t".join( headings )
- set_default_mode( NO_CONVERSION )
r_data_frame = r.read_table( tmp_file.name, header=True, sep="\t" )
outfile = open( outfile_name, 'w' )
@@ -105,7 +110,7 @@ def main():
stop_err( "Computation resulted in the following error: %s" % str( s ) )
summary = summary.as_py( BASIC_CONVERSION )
outfile.write( "#%s\n" % headings_str )
- outfile.write( "%s\n" % "\t".join( [ "%g" % ( summary[ k ] ) for k in headings ] ) )
+ outfile.write( "%s\n" % "\t".join( [ "%g" % k for k in summary ] ) )
outfile.close()
if skipped_lines: