Source code for music21.features.outputFormats
from __future__ import annotations
from music21 import environment
from music21 import exceptions21
environLocal = environment.Environment('features.outputFormats')
class OutputFormatException(exceptions21.Music21Exception):
pass
[docs]
class OutputFormat:
'''
Provide output for a DataSet, which is passed in as an initial argument.
'''
def __init__(self, dataSet=None):
# assume a two dimensional array
self.ext = None # store a file extension if necessary
# pass a data set object
self._dataSet = dataSet
[docs]
def getHeaderLines(self):
'''
Get the header as a list of lines.
'''
pass # define in subclass
[docs]
def getString(self, includeClassLabel=True, includeId=True, lineBreak=None):
pass # define in subclass
[docs]
def write(self, fp=None, includeClassLabel=True, includeId=True):
'''
Write the file. If not file path is given, a temporary file will be written.
'''
if fp is None:
fp = environLocal.getTempFile(suffix=self.ext)
if not str(fp).endswith(self.ext):
raise OutputFormatException('Could not get a temp file with the right extension')
with open(fp, 'w', encoding='utf-8') as f:
f.write(self.getString(includeClassLabel=includeClassLabel,
includeId=includeId))
return fp
[docs]
class OutputTabOrange(OutputFormat):
'''
Tab delimited file format used with Orange.
For more information, see:
https://orange3.readthedocs.io/projects/orange-data-mining-library/en/latest/tutorial/data.html#saving-the-data
'''
def __init__(self, dataSet=None):
super().__init__(dataSet=dataSet)
self.ext = '.tab'
[docs]
def getHeaderLines(self, includeClassLabel=True, includeId=True):
# noinspection PyShadowingNames
'''
Get the header as a list of lines.
>>> f = [features.jSymbolic.ChangesOfMeterFeature]
>>> ds = features.DataSet()
>>> ds.addFeatureExtractors(f)
>>> of = features.outputFormats.OutputTabOrange(ds)
>>> for x in of.getHeaderLines(): print(x)
['Identifier', 'Changes_of_Meter']
['string', 'discrete']
['meta', '']
>>> ds = features.DataSet(classLabel='Composer')
>>> ds.addFeatureExtractors(f)
>>> of = features.outputFormats.OutputTabOrange(ds)
>>> for x in of.getHeaderLines(): print(x)
['Identifier', 'Changes_of_Meter', 'Composer']
['string', 'discrete', 'discrete']
['meta', '', 'class']
'''
post = []
post.append(self._dataSet.getAttributeLabels(
includeClassLabel=includeClassLabel, includeId=includeId))
# second row metadata
row = []
for x in self._dataSet.getDiscreteLabels(
includeClassLabel=includeClassLabel, includeId=includeId):
if x is None: # this is a string entry
row.append('string')
elif x is True: # if True, it is discrete
row.append('discrete')
else:
row.append('continuous')
post.append(row)
# third row metadata
row = []
for x in self._dataSet.getClassPositionLabels(includeId=includeId):
if x is None: # the id value
row.append('meta')
elif x is True: # if True, it is the class column
row.append('class')
else:
row.append('')
post.append(row)
return post
[docs]
def getString(self, includeClassLabel=True, includeId=True, lineBreak=None):
'''
Get the complete DataSet as a string with the appropriate headers.
'''
if lineBreak is None:
lineBreak = '\n'
msg = []
header = self.getHeaderLines(includeClassLabel=includeClassLabel,
includeId=includeId)
data = header + self._dataSet.getFeaturesAsList(
includeClassLabel=includeClassLabel)
for row in data:
sub = []
for e in row:
sub.append(str(e))
msg.append('\t'.join(sub))
return lineBreak.join(msg)
[docs]
class OutputCSV(OutputFormat):
'''
Comma-separated value list.
'''
def __init__(self, dataSet=None):
super().__init__(dataSet=dataSet)
self.ext = '.csv'
[docs]
def getHeaderLines(self, includeClassLabel=True, includeId=True):
'''
Get the header as a list of lines.
>>> f = [features.jSymbolic.ChangesOfMeterFeature]
>>> ds = features.DataSet(classLabel='Composer')
>>> ds.addFeatureExtractors(f)
>>> of = features.outputFormats.OutputCSV(ds)
>>> of.getHeaderLines()[0]
['Identifier', 'Changes_of_Meter', 'Composer']
'''
post = []
post.append(self._dataSet.getAttributeLabels(
includeClassLabel=includeClassLabel, includeId=includeId))
return post
[docs]
def getString(self, includeClassLabel=True, includeId=True, lineBreak=None):
if lineBreak is None:
lineBreak = '\n'
msg = []
header = self.getHeaderLines(includeClassLabel=includeClassLabel,
includeId=includeId)
data = header + self._dataSet.getFeaturesAsList(
includeClassLabel=includeClassLabel, includeId=includeId)
for row in data:
sub = []
for e in row:
sub.append(str(e))
msg.append(','.join(sub))
return lineBreak.join(msg)
[docs]
class OutputARFF(OutputFormat):
'''
An ARFF (Attribute-Relation File Format) file.
See
https://web.archive.org/web/20160212022757/http://weka.wikispaces.com/ARFF+%28stable+version%29
for more details
>>> oa = features.outputFormats.OutputARFF()
>>> oa.ext
'.arff'
'''
def __init__(self, dataSet=None):
super().__init__(dataSet=dataSet)
self.ext = '.arff'
[docs]
def getHeaderLines(self, includeClassLabel=True, includeId=True):
'''
Get the header as a list of lines.
>>> f = [features.jSymbolic.ChangesOfMeterFeature]
>>> ds = features.DataSet(classLabel='Composer')
>>> ds.addFeatureExtractors(f)
>>> of = features.outputFormats.OutputARFF(ds)
>>> for x in of.getHeaderLines(): print(x)
@RELATION Composer
@ATTRIBUTE Identifier STRING
@ATTRIBUTE Changes_of_Meter NUMERIC
@ATTRIBUTE class {}
@DATA
'''
post = []
# get three parallel lists
attrs = self._dataSet.getAttributeLabels(
includeClassLabel=includeClassLabel, includeId=includeId)
discreteLabels = self._dataSet.getDiscreteLabels(
includeClassLabel=includeClassLabel, includeId=includeId)
classLabels = self._dataSet.getClassPositionLabels(includeId=includeId)
post.append(f'@RELATION {self._dataSet.getClassLabel()}')
for i, attrLabel in enumerate(attrs):
discrete = discreteLabels[i]
classLabel = classLabels[i]
if not classLabel: # a normal attribute
if discrete is None: # this is an identifier
post.append(f'@ATTRIBUTE {attrLabel} STRING')
elif discrete is True:
post.append(f'@ATTRIBUTE {attrLabel} NUMERIC')
else: # this needs to be a NOMINAL type
post.append(f'@ATTRIBUTE {attrLabel} NUMERIC')
else:
values = self._dataSet.getUniqueClassValues()
joined = ','.join(values)
post.append('@ATTRIBUTE class {' + joined + '}')
# include start of data declaration
post.append('@DATA')
return post
[docs]
def getString(self, includeClassLabel=True, includeId=True, lineBreak=None):
if lineBreak is None:
lineBreak = '\n'
msg = []
header = self.getHeaderLines(includeClassLabel=includeClassLabel,
includeId=includeId)
for row in header:
msg.append(row)
data = self._dataSet.getFeaturesAsList(
includeClassLabel=includeClassLabel)
# data is separated by commas
for row in data:
sub = []
for e in row:
sub.append(str(e))
msg.append(','.join(sub))
return lineBreak.join(msg)
if __name__ == '__main__':
import music21
music21.mainTest()