diff --git a/README.rst b/README.rst
index 356b3277..d19e7c77 100644
--- a/README.rst
+++ b/README.rst
@@ -109,7 +109,7 @@ You can also follow this `interactive Jupyter tutorial <https://mybinder.org/v2/
 We also provide utilities for:
 
 -  `converting between machine learning toolkit formats <https://skll.readthedocs.org/en/latest/utilities.html#skll-convert>`__
-   (e.g., ARFF, CSV, MegaM)
+   (e.g., ARFF, CSV)
 -  `filtering feature files <https://skll.readthedocs.org/en/latest/utilities.html#filter-features>`__
 -  `joining feature files <https://skll.readthedocs.org/en/latest/utilities.html#join-features>`__
 -  `other common tasks <https://skll.readthedocs.org/en/latest/utilities.html>`__
diff --git a/doc/api/quickstart.rst b/doc/api/quickstart.rst
index b01f0f49..8ff71999 100644
--- a/doc/api/quickstart.rst
+++ b/doc/api/quickstart.rst
@@ -7,10 +7,10 @@ Load a ``FeatureSet`` from a file::
 
     from skll.data import Reader
 
-    example_reader = Reader.for_path('myexamples.megam')
+    example_reader = Reader.for_path('myexamples.csv')
     train_examples = example_reader.read()
 
-    
+
 Or, work with an existing ``pandas`` ``DataFrame``::
 
     from skll import FeatureSet
diff --git a/doc/run_experiment.rst b/doc/run_experiment.rst
index 0cca5d6c..7dde4b6a 100644
--- a/doc/run_experiment.rst
+++ b/doc/run_experiment.rst
@@ -111,8 +111,8 @@ When using this file format, the following restrictions apply:
 *   All other columns contain feature values, and every feature value
     must be specified (making this a poor choice for sparse data).
 
-.. warning:: 
- 
+.. warning::
+
     1. SKLL will raise an error if there are blank values in **any** of the
        columns. You must either drop all rows with blank values in any column
        or replace the blanks with a value you specify. To drop or replace via
@@ -137,8 +137,8 @@ When using this file format, the following restrictions apply:
             $ filter_features --id_col ID --label_col CLASS --drop_blanks temp.csv out.csv
 
        If you are using the SKLL Reader API, you can accomplish the same in a
-       single step by also passing using the keyword argument ``pandas_kwargs`` 
-       when instantiating either a :py:mod:`skll.data.readers.CSVReader` or a 
+       single step by also passing using the keyword argument ``pandas_kwargs``
+       when instantiating either a :py:mod:`skll.data.readers.CSVReader` or a
        :py:mod:`skll.data.readers.TSVReader`. For our example:
 
         .. code-block:: python
@@ -150,7 +150,7 @@ When using this file format, the following restrictions apply:
                                    pandas_kwargs={'usecols': ['A', 'C', 'F', 'ID', 'CLASS']})
             fs = r.read()
 
-       Make sure to include the ID and label columns in the `usecols` list 
+       Make sure to include the ID and label columns in the `usecols` list
        otherwise ``pandas`` will drop them too.
 
 .. _ndj:
@@ -193,34 +193,11 @@ The entire format would like this::
     IDs, labels, and feature names cannot contain the following
     characters:  ``|`` ``#`` ``=``
 
-.. _megam:
-
-megam
-^^^^^
-
-An expanded form of the input format for the
-`MegaM classification package <http://users.umiacs.umd.edu/~hal/megam/>`__ with
-the ``-fvals`` switch.
-
-The basic format is::
-
-    # Instance1
-    CLASS1    F0 2.5 F1 3 FEATURE_2 -152000
-    # Instance2
-    CLASS2    F1 7.524
-
-where the **optional** comments before each instance specify the ID for the
-following line, class names are separated from feature-value pairs with a tab,
-and feature-value pairs are separated by spaces. Any omitted features for a
-given instance are assumed to be zero, so this format is handy when dealing
-with sparse data. We also include several utility scripts for converting
-to/from this MegaM format and for adding/removing features from the files.
 
 .. _create_config:
 
 Configuration file fields
 -------------------------
-
 The experiment configuration files that ``run_experiment`` accepts are standard
 `Python configuration files <https://docs.python.org/3/library/configparser.html>`__
 that are similar in format to Windows INI files. [#]_
@@ -882,22 +859,22 @@ Available metrics are:
 
     **Classification:** The following objectives can be used for classification problems although some are restricted by problem type (binary/multiclass), types of labels (integers/floats/strings), and whether they are contiguous (if integers). Please read carefully.
 
-    .. note:: When doing classification, SKLL internally sorts and maps all the class 
+    .. note:: When doing classification, SKLL internally sorts and maps all the class
               labels in the data and maps them to integers which can be thought
               of class indices. This happens irrespective of the data type of the
               original labels. For example, if your data has the labels ``['A', 'B', 'C']``,
               SKLL will map them to the indices ``[0, 1, 2]`` respectively. It will do the
-              same if you have integer labels (``[1, 2, 3]``) or floating point ones 
+              same if you have integer labels (``[1, 2, 3]``) or floating point ones
               (``[1.0, 1.1, 1.2]``). All of the tuning objectives are computed using
               these integer indices rather than the original class labels. This is why
               some metrics *only* make sense in certain scenarios. For example, SKLL
               only allows using weighted kappa metrics as tuning objectives if the original
-              class labels are contiguous integers, e.g., ``[1, 2, 3]`` or ``[4, 5, 6]`` 
-              -- or even integer-like floats (e,g., ``[1.0, 2.0, 3.0]``, but not 
+              class labels are contiguous integers, e.g., ``[1, 2, 3]`` or ``[4, 5, 6]``
+              -- or even integer-like floats (e,g., ``[1.0, 2.0, 3.0]``, but not
               ``[1.0, 1.1, 1.2]``).
 
 
-    *   **accuracy**: Overall `accuracy <https://scikit-learn.org/stable/modules/generated/sklearn.metrics.accuracy_score.html>`__ 
+    *   **accuracy**: Overall `accuracy <https://scikit-learn.org/stable/modules/generated/sklearn.metrics.accuracy_score.html>`__
     *   **average_precision**: `Area under PR curve <https://scikit-learn.org/stable/modules/generated/sklearn.metrics.average_precision_score.html>`__ . To use this metric, :ref:`probability <probability>` must be set to ``True``. (*Binary classification only*).
     *   **balanced_accuracy**: A version of accuracy `specifically designed <https://scikit-learn.org/stable/modules/generated/sklearn.metrics.balanced_accuracy_score.html#sklearn.metrics.balanced_accuracy_score>`__ for imbalanced binary and multi-class scenarios.
     *   **f1**: The default scikit-learn |F1 link|_
@@ -913,26 +890,26 @@ Available metrics are:
     *   **lwk_off_by_one**: Same as ``linear_weighted_kappa``, but all
         ranking differences are discounted by one. (*Contiguous integer labels only*).
     *   **neg_log_loss**: The negative of the classification `log loss <https://scikit-learn.org/stable/modules/generated/sklearn.metrics.log_loss.html>`__ . Since scikit-learn `recommends <https://scikit-learn.org/stable/modules/model_evaluation.html#common-cases-predefined-values>`__ using negated loss functions as scorer functions, SKLL does the same for the sake of consistency. To use this metric, :ref:`probability <probability>` must be set to ``True``.
-    *   **pearson**: `Pearson correlation <https://en.wikipedia.org/wiki/Pearson_product-moment_correlation_coefficient>`__ . For binary classification and with :ref:`probability <probability>` set to ``True``, the probabilities for the positive class will be used to compute the correlation values. In all other cases, the labels are used. (*Integer labels only*). 
+    *   **pearson**: `Pearson correlation <https://en.wikipedia.org/wiki/Pearson_product-moment_correlation_coefficient>`__ . For binary classification and with :ref:`probability <probability>` set to ``True``, the probabilities for the positive class will be used to compute the correlation values. In all other cases, the labels are used. (*Integer labels only*).
     *   **precision**: `Precision <https://scikit-learn.org/stable/modules/generated/sklearn.metrics.precision_score.html>`__
-    *   **quadratic_weighted_kappa**: `Quadratic weighted kappa <http://www.vassarstats.net/kappaexp.html>`__. (*Contiguous integer labels only*). 
+    *   **quadratic_weighted_kappa**: `Quadratic weighted kappa <http://www.vassarstats.net/kappaexp.html>`__. (*Contiguous integer labels only*).
     *   **qwk_off_by_one**: Same as ``quadratic_weighted_kappa``, but all
-        ranking differences are discounted by one. (*Contiguous integer labels only*). 
+        ranking differences are discounted by one. (*Contiguous integer labels only*).
     *   **recall**: `Recall <https://scikit-learn.org/stable/modules/generated/sklearn.metrics.recall_score.html>`__
     *   **roc_auc**: `Area under ROC curve <https://scikit-learn.org/stable/modules/generated/sklearn.metrics.roc_auc_score.html>`__ .To use this metric, :ref:`probability <probability>` must be set to ``True``. (*Binary classification only*).
     *   **spearman**: `Spearman rank-correlation <https://en.wikipedia.org/wiki/Spearman's_rank_correlation_coefficient>`__. For binary classification and with :ref:`probability <probability>` set to ``True``, the probabilities for the positive class will be used to compute the correlation values. In all other cases, the labels are used. (*Integer labels only*).
-    *   **unweighted_kappa**: Unweighted `Cohen's kappa <https://en.wikipedia.org/wiki/Cohen's_kappa>`__. 
+    *   **unweighted_kappa**: Unweighted `Cohen's kappa <https://en.wikipedia.org/wiki/Cohen's_kappa>`__.
     *   **uwk_off_by_one**: Same as ``unweighted_kappa``, but all ranking
         differences are discounted by one. In other words, a ranking of
-        1 and a ranking of 2 would be considered equal. 
+        1 and a ranking of 2 would be considered equal.
 
 .. |F1 link| replace:: F\ :sub:`1` score
 .. _F1 link: https://scikit-learn.org/stable/modules/generated/sklearn.metrics.f1_score.html
 
-    **Regression:** The following objectives can be used for regression problems. 
+    **Regression:** The following objectives can be used for regression problems.
 
     *   **explained_variance**: A `score <https://scikit-learn.org/stable/modules/generated/sklearn.metrics.explained_variance_score.html#sklearn.metrics.explained_variance_score>`__ indicating how much of the variance in the given data can be by the model.
-    *   **kendall_tau**: `Kendall's tau <https://en.wikipedia.org/wiki/Kendall_tau_rank_correlation_coefficient>`__ 
+    *   **kendall_tau**: `Kendall's tau <https://en.wikipedia.org/wiki/Kendall_tau_rank_correlation_coefficient>`__
     *   **linear_weighted_kappa**: Linear weighted kappa (any floating point values are rounded to ints)
     *   **lwk_off_by_one**: Same as ``linear_weighted_kappa``, but all
         ranking differences are discounted by one.
diff --git a/doc/tutorial.rst b/doc/tutorial.rst
index e5792eab..2c563360 100644
--- a/doc/tutorial.rst
+++ b/doc/tutorial.rst
@@ -4,7 +4,7 @@
 Tutorial
 ========
 
-Before doing anything below, you'll want to :ref:`install SKLL <install>`.
+Before doing anything below, you'll want to :ref:`install SKLL <install>`. 
 
 Workflow
 --------
@@ -198,7 +198,7 @@ An example of a human-readable results file for our Titanic experiment is::
     Total Time: 0:00:04.579588
 
 
-    Fold: 
+    Fold:
     Model Parameters: {"bootstrap": true, "ccp_alpha": 0.0, "class_weight": null, "criterion": "gini", "max_depth": 5, "max_features": "auto", "max_leaf_nodes": null, "max_samples": null, "min_impurity_decrease": 0.0, "min_impurity_split": null, "min_samples_leaf": 1, "min_samples_split": 2, "min_weight_fraction_leaf": 0.0, "n_estimators": 500, "n_jobs": null, "oob_score": false, "random_state": 123456789, "verbose": 0, "warm_start": false}
     Grid Objective Score (Train) = 0.797874315418175
     +----+------+------+-------------+----------+-------------+
diff --git a/doc/utilities.rst b/doc/utilities.rst
index 425d190b..9241e7f7 100644
--- a/doc/utilities.rst
+++ b/doc/utilities.rst
@@ -53,7 +53,7 @@ Positional Arguments
 .. option:: infile
 
     Input feature file (ends in ``.arff``, ``.csv``, ``.jsonlines``,
-    ``.megam``, ``.ndj``, or ``.tsv``)
+    ``.ndj``, or ``.tsv``)
 
 .. option:: outfile
 
@@ -93,7 +93,7 @@ Optional Arguments
     (default: ``y``)
 
 .. option:: -db, --drop-blanks
-    
+
     Drop all lines/rows that have any blank values.
     (default: ``False``)
 
@@ -102,7 +102,7 @@ Optional Arguments
     Specifies a new value with which to replace blank values in all columns in the
     file. To replace blanks differently in each column, use the SKLL Reader API directly.
     (default: ``None``)
-      
+
 .. option:: -q, --quiet
 
     Suppress printing of ``"Loading..."`` messages.
@@ -124,11 +124,11 @@ Useful if you want to reuse a trained model as part of a larger system without
 creating configuration files. Offers the following modes of operation:
 
 - For non-probabilistic classification and regression, generate the predictions.
-- For probabilistic classification, generate either the most likely labels 
+- For probabilistic classification, generate either the most likely labels
   or the probabilities for each class label.
 - For binary probablistic classification, generate the positive class label
   only if its probability exceeds the given threshold. The positive class
-  label is either read from the model file or inferred the same way as 
+  label is either read from the model file or inferred the same way as
   a SKLL learner would.
 
 Positional Arguments
@@ -139,7 +139,8 @@ Positional Arguments
 
 .. option:: input_file(s)
 
-    One or more csv file(s), jsonlines file(s), or megam file(s) (with or without the
+    One or more feature file(s) (ending in ``.arff``, ``.csv``, ``.jsonlines``,
+    ``.libsvm``, ``.ndj``, or ``.tsv``) (with or without the
     label column), with the appropriate suffix.
 
 Optional Arguments
@@ -152,17 +153,17 @@ Optional Arguments
 .. option:: -l <label_col>, --label_col <label_col>
 
     Name of the column which contains the labels in ARFF, CSV, or TSV files.
-    For ARFF files, this must be the final column to count as the label. 
+    For ARFF files, this must be the final column to count as the label.
     (default: ``y``)
 
 .. option:: -o <path>, --output_file <path>
-    
+
     Path to output TSV file. If not specified, predictions will be printed
     to stdout. For probabilistic binary classification, the probability of
     the positive class will always be in the last column.
-    
+
 .. option:: -p, --predict_labels
-    
+
     If the model does probabilistic classification, output the class label
     with the highest probability instead of the class probabilities.
 
@@ -172,7 +173,7 @@ Optional Arguments
 
 .. option:: -t <threshold>, --threshold <threshold>
 
-    If the model does binary probabilistic classification, 
+    If the model does binary probabilistic classification,
     return the positive class label only if it meets/exceeds
     the given threshold and the other class label otherwise.
 
@@ -195,7 +196,7 @@ Positional Arguments
 .. option:: infile ...
 
     Input feature files (ends in ``.arff``, ``.csv``, ``.jsonlines``,
-    ``.megam``, ``.ndj``, or ``.tsv``)
+    ``.ndj``, or ``.tsv``)
 
 .. option:: outfile
 
@@ -287,7 +288,7 @@ skll_convert
 ------------
 .. program:: skll_convert
 
-Convert between .arff, .csv., .jsonlines, .libsvm, .megam, and .tsv formats.
+Convert between .arff, .csv., .jsonlines, .libsvm, and .tsv formats.
 
 Positional Arguments
 ^^^^^^^^^^^^^^^^^^^^
@@ -295,12 +296,12 @@ Positional Arguments
 .. option:: infile
 
     Input feature file (ends in ``.arff``, ``.csv``, ``.jsonlines``,
-    ``.libsvm``, ``.megam``, ``.ndj``, or ``.tsv``)
+    ``.libsvm``, ``.ndj``, or ``.tsv``)
 
 .. option:: outfile
 
     Output feature file (ends in ``.arff``, ``.csv``, ``.jsonlines``,
-    ``.libsvm``, ``.megam``, ``.ndj``, or ``.tsv``)
+    ``.libsvm``, ``.ndj``, or ``.tsv``)
 
 
 Optional Arguments
diff --git a/skll/data/__init__.py b/skll/data/__init__.py
index 294d7c8f..0651fd27 100644
--- a/skll/data/__init__.py
+++ b/skll/data/__init__.py
@@ -11,10 +11,10 @@ Handles reading and writing data from various types of data files.
 import csv
 
 from .featureset import FeatureSet
-from .readers import (ARFFReader, CSVReader, LibSVMReader, MegaMReader,
+from .readers import (ARFFReader, CSVReader, LibSVMReader,
                       NDJReader, TSVReader, safe_float, Reader)
 from .writers import (ARFFWriter, Writer, TSVWriter, CSVWriter,
-                      LibSVMWriter, MegaMWriter, NDJWriter)
+                      LibSVMWriter, NDJWriter)
 
 
 # Register dialect for handling ARFF files
@@ -24,6 +24,6 @@ csv.register_dialect('arff', delimiter=',', quotechar="'",
 
 
 __all__ = ['Reader', 'safe_float', 'FeatureSet', 'ARFFReader',
-           'CSVReader', 'LibSVMReader', 'MegaMReader', 'NDJReader',
+           'CSVReader', 'LibSVMReader', 'NDJReader',
            'TSVReader', 'ARFFWriter', 'LibSVMWriter', 'TSVWriter',
-           'CSVWriter', 'MegaMWriter', 'NDJWriter', 'Writer']
+           'CSVWriter', 'NDJWriter', 'Writer']
diff --git a/skll/data/readers.py b/skll/data/readers.py
index 0420ed53..74e8a4a7 100644
--- a/skll/data/readers.py
+++ b/skll/data/readers.py
@@ -164,7 +164,7 @@ class Reader(object):
             ext = '.' + path_or_list.rsplit('.', 1)[-1].lower()
             if ext not in EXT_TO_READER:
                 raise ValueError(('Example files must be in either .arff, '
-                                  '.csv, .jsonlines, .megam, .ndj, or .tsv '
+                                  '.csv, .jsonlines, .ndj, or .tsv '
                                   'format. You specified: '
                                   '{}').format(path_or_list))
         return EXT_TO_READER[ext](path_or_list, **kwargs)
@@ -400,7 +400,7 @@ class Reader(object):
     def read(self):
         """
         Loads examples in the `.arff`, `.csv`, `.jsonlines`, `.libsvm`,
-        `.megam`, `.ndj`, or `.tsv` formats.
+        `.ndj`, or `.tsv` formats.
 
         Returns
         -------
@@ -552,7 +552,7 @@ class NDJReader(Reader):
             # Process good lines
             example = json.loads(line)
             # Convert all IDs to strings initially,
-            # for consistency with csv and megam formats.
+            # for consistency with csv formats.
             curr_id = str(example.get("id",
                                       "EXAMPLE_{}".format(example_num)))
             class_name = (safe_float(example['y'],
@@ -571,92 +571,6 @@ class NDJReader(Reader):
             yield curr_id, class_name, example
 
 
-class MegaMReader(Reader):
-
-    """
-    Reader to create a ``FeatureSet`` instance from  a MegaM -fvals file.
-    If example/instance IDs are included in the files, they must be specified
-    as a comment line directly preceding the line with feature values.
-    """
-
-    def _sub_read(self, file):
-        """
-        Parameters
-        ----------
-        file : file buffer
-            A file buffer for an MegaM file.
-
-        Yields
-        ------
-        curr_id : str
-            The current ID for the example.
-        class_name : float or str
-            The name of the class label for the example.
-        example : dict
-            The example valued in dictionary format, with 'x'
-            as list of features.
-
-        Raises
-        ------
-        ValueError
-            If there are duplicate feature names.
-        """
-        example_num = 0
-        curr_id = 'EXAMPLE_0'
-        for line in file:
-            # Process encoding
-            if not isinstance(line, str):
-                line = UnicodeDammit(line, ['utf-8',
-                                            'windows-1252']).unicode_markup
-            line = line.strip()
-            # Handle instance lines
-            if line.startswith('#'):
-                curr_id = line[1:].strip()
-            elif line and line not in ['TRAIN', 'TEST', 'DEV']:
-                split_line = line.split()
-                num_cols = len(split_line)
-                del line
-                # Line is just a class label
-                if num_cols == 1:
-                    class_name = safe_float(split_line[0],
-                                            replace_dict=self.class_map)
-                    field_pairs = []
-                # Line has a class label and feature-value pairs
-                elif num_cols % 2 == 1:
-                    class_name = safe_float(split_line[0],
-                                            replace_dict=self.class_map)
-                    field_pairs = split_line[1:]
-                # Line just has feature-value pairs
-                elif num_cols % 2 == 0:
-                    class_name = None
-                    field_pairs = split_line
-
-                curr_info_dict = {}
-                if len(field_pairs) > 0:
-                    # Get current instances feature-value pairs
-                    field_names = islice(field_pairs, 0, None, 2)
-                    # Convert values to floats, because otherwise
-                    # features'll be categorical
-                    field_values = (safe_float(val) for val in
-                                    islice(field_pairs, 1, None, 2))
-
-                    # Add the feature-value pairs to dictionary
-                    curr_info_dict.update(zip(field_names, field_values))
-
-                    if len(curr_info_dict) != len(field_pairs) / 2:
-                        raise ValueError(('There are duplicate feature ' +
-                                          'names in {} for example ' +
-                                          '{}.').format(self.path_or_list,
-                                                        curr_id))
-
-                yield curr_id, class_name, curr_info_dict
-
-                # Set default example ID for next instance, in case we see a
-                # line without an ID.
-                example_num += 1
-                curr_id = 'EXAMPLE_{}'.format(example_num)
-
-
 class LibSVMReader(Reader):
 
     """
@@ -1147,6 +1061,5 @@ EXT_TO_READER = {".arff": ARFFReader,
                  ".csv": CSVReader,
                  ".jsonlines": NDJReader,
                  ".libsvm": LibSVMReader,
-                 ".megam": MegaMReader,
                  '.ndj': NDJReader,
                  ".tsv": TSVReader}
diff --git a/skll/data/writers.py b/skll/data/writers.py
index adf2fe91..c601cc48 100644
--- a/skll/data/writers.py
+++ b/skll/data/writers.py
@@ -32,7 +32,7 @@ class Writer(object):
     path : str
         A path to the feature file we would like to create. The suffix
         to this filename must be ``.arff``, ``.csv``, ``.jsonlines``,
-        ``.libsvm``, ``.megam``, ``.ndj``, or ``.tsv``. If ``subsets``
+        ``.libsvm``, ``.ndj``, or ``.tsv``. If ``subsets``
         is not ``None``, when calling the ``write()`` method, path is
         assumed to be a string containing the path to the directory to
         write the feature files with an additional file extension
@@ -91,7 +91,7 @@ class Writer(object):
         path : str
             A path to the feature file we would like to create. The
             suffix to this filename must be ``.arff``, ``.csv``,
-            ``.jsonlines``, ``.libsvm``, ``.megam``, ``.ndj``, or
+            ``.jsonlines``, ``.libsvm``, ``.ndj``, or
             ``.tsv``. If ``subsets`` is not ``None``, when calling the
             ``write()`` method, path is assumed to be a string
             containing the path to the directory to write the feature
@@ -586,60 +586,6 @@ class ARFFWriter(Writer):
         self._dict_writer.writerow(feat_dict)
 
 
-class MegaMWriter(Writer):
-
-    """
-    Writer for writing out FeatureSets as MegaM files.
-    """
-
-    @staticmethod
-    def _replace_non_ascii(line):
-        """
-        Parameters
-        ----------
-        line : str
-            The line to clean up.
-
-        Returns
-        -------
-        char_list : str
-            Copy of line with all non-ASCII characters replaced with
-            <U1234> sequences where 1234 is the value of ord() for the character.
-        """
-        char_list = []
-        for char in line:
-            char_num = ord(char)
-            char_list.append(
-                '<U{}>'.format(char_num) if char_num > 127 else char)
-        return ''.join(char_list)
-
-    def _write_line(self, id_, label_, feat_dict, output_file):
-        """
-        Write the current line in the file in MegaM format.
-
-        Parameters
-        ----------
-        id_ : str
-            The ID for the current instance.
-        label_ : str
-            The label for the current instance.
-        feat_dict : dict
-            The feature dictionary for the current instance.
-        output_file : file buffer
-            The file being written to.
-        """
-        # Don't try to add class column if this is label-less data
-        print('# {}'.format(id_), file=output_file)
-        if self.feat_set.has_labels:
-            print('{}'.format(label_), end='\t', file=output_file)
-        print(self._replace_non_ascii(' '.join(('{} {}'.format(field,
-                                                               value) for
-                                                field, value in
-                                                sorted(feat_dict.items()) if
-                                                Decimal(value) != 0))),
-              file=output_file)
-
-
 class NDJWriter(Writer):
 
     """
@@ -762,7 +708,7 @@ class LibSVMWriter(Writer):
 
     def _write_line(self, id_, label_, feat_dict, output_file):
         """
-        Write the current line in the file in MegaM format.
+        Write the current line in the file in this Writer's format.
 
         Parameters
         ----------
@@ -811,6 +757,5 @@ EXT_TO_WRITER = {".arff": ARFFWriter,
                  ".csv": CSVWriter,
                  ".jsonlines": NDJWriter,
                  ".libsvm": LibSVMWriter,
-                 ".megam": MegaMWriter,
                  '.ndj': NDJWriter,
                  ".tsv": TSVWriter}
diff --git a/skll/utils/commandline/filter_features.py b/skll/utils/commandline/filter_features.py
index 8fb51faa..4a4990b9 100755
--- a/skll/utils/commandline/filter_features.py
+++ b/skll/utils/commandline/filter_features.py
@@ -36,7 +36,7 @@ def main(argv=None):
         formatter_class=argparse.ArgumentDefaultsHelpFormatter)
     parser.add_argument('infile',
                         help='input feature file (ends in .arff, .csv, '
-                             '.jsonlines, .megam, .ndj, or .tsv)')
+                             '.jsonlines, .ndj, or .tsv)')
     parser.add_argument('outfile',
                         help='output feature file (must have same extension as '
                              'input file)')
@@ -104,7 +104,7 @@ def main(argv=None):
 
     if input_extension not in valid_extensions:
         logger.error(('Input file must be in either .arff, .csv, .jsonlines, '
-                      '.megam, .ndj, or .tsv format. You specified: '
+                      '.ndj, or .tsv format. You specified: '
                       '{}').format(input_extension))
         sys.exit(1)
 
diff --git a/skll/utils/commandline/generate_predictions.py b/skll/utils/commandline/generate_predictions.py
index 275c5848..19d74db5 100755
--- a/skll/utils/commandline/generate_predictions.py
+++ b/skll/utils/commandline/generate_predictions.py
@@ -143,7 +143,7 @@ def main(argv=None):
         input_extension = os.path.splitext(input_file)[1].lower()
         if input_extension not in EXT_TO_READER:
             logger.error(('Input file must be in either .arff, .csv, '
-                          '.jsonlines, .libsvm, .megam, .ndj, or .tsv format. '
+                          '.jsonlines, .libsvm, .ndj, or .tsv format. '
                           ' Skipping file {}').format(input_file))
             continue
         else:
diff --git a/skll/utils/commandline/join_features.py b/skll/utils/commandline/join_features.py
index 2a18d50b..afa3981d 100755
--- a/skll/utils/commandline/join_features.py
+++ b/skll/utils/commandline/join_features.py
@@ -35,7 +35,7 @@ def main(argv=None):
         formatter_class=argparse.ArgumentDefaultsHelpFormatter)
     parser.add_argument('infile',
                         help='input feature file (ends in .arff, .csv, \
-                              .jsonlines, .megam, .ndj, or .tsv)',
+                              .jsonlines, .ndj, or .tsv)',
                         nargs='+')
     parser.add_argument('outfile',
                         help='output feature file')
@@ -79,7 +79,7 @@ def main(argv=None):
 
     if input_extension not in valid_extensions:
         logger.error(('Input files must be in either .arff, .csv, .jsonlines, '
-                      '.megam, .ndj, or .tsv format. You specified: '
+                      '.ndj, or .tsv format. You specified: '
                       '{}').format(input_extension))
         sys.exit(1)
 
diff --git a/skll/utils/commandline/skll_convert.py b/skll/utils/commandline/skll_convert.py
index 813ef083..75ecfe52 100755
--- a/skll/utils/commandline/skll_convert.py
+++ b/skll/utils/commandline/skll_convert.py
@@ -53,10 +53,10 @@ def main(argv=None):
         formatter_class=argparse.ArgumentDefaultsHelpFormatter)
     parser.add_argument('infile',
                         help='input feature file (ends in .arff, .csv, \
-                              .jsonlines, .libsvm, .megam, .ndj, or .tsv)')
+                              .jsonlines, .libsvm, .ndj, or .tsv)')
     parser.add_argument('outfile',
                         help='output feature file (ends in .arff, .csv, \
-                              .jsonlines, .libsvm, .megam, .ndj, or .tsv)')
+                              .jsonlines, .libsvm, .ndj, or .tsv)')
     parser.add_argument('-i', '--id_col',
                         help='Name of the column which contains the instance \
                               IDs in ARFF, CSV, or TSV files.',
@@ -106,7 +106,7 @@ def main(argv=None):
 
     if input_extension not in EXT_TO_READER:
         logger.error(('Input file must be in either .arff, .csv, .jsonlines, '
-                      '.libsvm, .megam, .ndj, or .tsv format. You specified: '
+                      '.libsvm, .ndj, or .tsv format. You specified: '
                       '{}').format(input_extension))
         sys.exit(1)
 

Remove MegaM Reader and Writer classes?
Is anyone using the `MegaMReader` or `MegaMWriter` classes in `skll`? If not, is there any reason to maintain these? Is there any code that still uses the megam classifier?

@aoifecahill  @desilinguist @mulhod @bndgyawali?
diff --git a/tests/test_commandline_utils.py b/tests/test_commandline_utils.py
index 4215ee6c..ccb3b0c8 100644
--- a/tests/test_commandline_utils.py
+++ b/tests/test_commandline_utils.py
@@ -50,7 +50,6 @@ import skll.utils.commandline.plot_learning_curves as plc
 from skll.data import (FeatureSet,
                        NDJWriter,
                        LibSVMWriter,
-                       MegaMWriter,
                        LibSVMReader,
                        safe_float)
 from skll.data.readers import EXT_TO_READER
@@ -590,7 +589,7 @@ def test_generate_predictions_console_bad_input_ext():
 
     expected_log_mssg = ("skll.utils.commandline.generate_predictions: ERROR: Input "
                          "file must be in either .arff, .csv, .jsonlines, "
-                         ".libsvm, .megam, .ndj, or .tsv format.  Skipping "
+                         ".libsvm, .ndj, or .tsv format.  Skipping "
                          "file fake_input_file.txt")
 
     eq_(lc.handler.buffer[-1], expected_log_mssg)
@@ -678,6 +677,7 @@ def test_generate_predictions_threshold_non_probabilistic():
     learner.save(model_file)
 
     # now call main() from generate_predictions.py
+
     generate_cmd = [model_file, input_file]
     generate_cmd.append("-t 0.6")
     gp.main(generate_cmd)
@@ -827,7 +827,6 @@ def test_skll_convert():
                                                '.csv',
                                                '.jsonlines',
                                                '.libsvm',
-                                               '.megam',
                                                '.tsv'], 2):
         yield check_skll_convert, from_suffix, to_suffix, 'string'
         yield check_skll_convert, from_suffix, to_suffix, 'integer_string'
@@ -862,12 +861,6 @@ def test_skll_convert_libsvm_map():
     swapped_fs.features[:, 0] = swapped_fs.features[:, 1]
     swapped_fs.features[:, 1] = tmp
 
-    # now write out this new feature set as a MegaM file
-    swapped_megam_file = join(_my_dir, 'other',
-                              'test_skll_convert_libsvm_map.megam')
-    writer = MegaMWriter(swapped_megam_file, swapped_fs, quiet=True)
-    writer.write()
-
     # now run skll_convert to convert this into a libsvm file
     # but using the mapping specified in the first libsvm file
     converted_libsvm_file = join(_my_dir, 'other',
@@ -1455,7 +1448,7 @@ def check_filter_features_no_arff_argparse(extension, filter_type,
 def test_filter_features_no_arff_argparse():
     for (extension, filter_type, id_col,
          label_col, inverse, quiet) in product(['.jsonlines', '.ndj',
-                                                '.megam', '.tsv',
+                                                '.tsv',
                                                 '.csv', ],
                                                ['feature', 'id',
                                                 'label'],
@@ -1633,7 +1626,7 @@ def check_filter_features_raises_system_exit(cmd_args):
 def test_filter_features_unmatched_formats():
     # Make sure filter_feature exits when the output file is in a different
     # format
-    for inext, outext in combinations(['.arff', '.megam', '.ndj', '.tsv',
+    for inext, outext in combinations(['.arff', '.ndj', '.tsv',
                                        '.jsonlines', '.csv'], 2):
         ff_cmd_args = ['foo{}'.format(inext), 'bar{}'.format(outext), '-f',
                        'a', 'b', 'c']
@@ -1714,7 +1707,7 @@ def check_join_features_argparse(extension, label_col='y', id_col='id',
 
 def test_join_features_argparse():
     for (extension, label_col, id_col, quiet) in product(['.jsonlines', '.ndj',
-                                                          '.megam', '.tsv',
+                                                          '.tsv',
                                                           '.csv', '.arff'],
                                                          ['y', 'foo'],
                                                          ['id', 'id_foo'],
@@ -1776,7 +1769,7 @@ def check_join_features_raises_system_exit(cmd_args):
 def test_join_features_unmatched_input_formats():
     # Make sure that join_feature exits when the input files are in different
     # formats
-    for ext1, ext2 in combinations(['.arff', '.megam', '.ndj', '.tsv',
+    for ext1, ext2 in combinations(['.arff', '.ndj', '.tsv',
                                     '.jsonlines', '.csv'], 2):
         jf_cmd_args = ['foo{}'.format(ext1), 'bar{}'.format(ext2),
                        'baz{}'.format(ext1)]
@@ -1786,7 +1779,7 @@ def test_join_features_unmatched_input_formats():
 def test_join_features_unmatched_output_format():
     # Make sure join_features exits when the output file is in a different
     # format
-    for ext1, ext2 in combinations(['.arff', '.megam', '.ndj', '.tsv',
+    for ext1, ext2 in combinations(['.arff', '.ndj', '.tsv',
                                     '.jsonlines', '.csv'], 2):
         jf_cmd_args = ['foo{}'.format(ext1), 'bar{}'.format(ext1),
                        'baz{}'.format(ext2)]
diff --git a/tests/test_featureset.py b/tests/test_featureset.py
index 5b86465f..f9913b5f 100644
--- a/tests/test_featureset.py
+++ b/tests/test_featureset.py
@@ -62,7 +62,7 @@ def tearDown():
     """
     Clean up files created during testing.
     """
-    for filetype in ['csv', 'jsonlines', 'libsvm', 'megam', 'tsv']:
+    for filetype in ['csv', 'jsonlines', 'libsvm', 'tsv']:
         filepath = join(_my_dir, 'other', 'empty.{}'.format(filetype))
         if exists(filepath):
             os.unlink(filepath)
@@ -110,9 +110,9 @@ def check_empty_file_read(filetype, reader_type):
 
 def test_empty_file_read():
     for filetype, reader_type in zip(['csv', 'jsonlines',
-                                      'libsvm', 'megam', 'tsv'],
+                                      'libsvm', 'tsv'],
                                      ['CSVReader', 'NDJReader',
-                                      'LibSVMReader', 'MegaMReader', 'TSVReader']):
+                                      'LibSVMReader', 'TSVReader']):
         yield check_empty_file_read, filetype, reader_type
 
 
@@ -747,10 +747,10 @@ def check_load_featureset(suffix, numeric_ids):
 
 def test_load_featureset():
     # Test merging with numeric IDs
-    for suffix in ['.jsonlines', '.ndj', '.megam', '.tsv', '.csv', '.arff']:
+    for suffix in ['.jsonlines', '.ndj', '.tsv', '.csv', '.arff']:
         yield check_load_featureset, suffix, True
 
-    for suffix in ['.jsonlines', '.ndj', '.megam', '.tsv', '.csv', '.arff']:
+    for suffix in ['.jsonlines', '.ndj', '.tsv', '.csv', '.arff']:
         yield check_load_featureset, suffix, False
 
 
@@ -807,13 +807,7 @@ def make_conversion_data(num_feat_files, from_suffix, to_suffix, with_labels=Tru
     for j in range(num_examples):
         y = "dog" if j % 2 == 0 else "cat"
         ex_id = "{}{}".format(y, j)
-        # if we are not using labels, we do not want zero-valued features
-        # because it may be the case that some subset of features end up
-        # being all 0 and if this subset ends up being written out to a file
-        # below, then for some formats (e.g., megam) nothing will get written
-        # out which can cause issues when reading this file
-        lowest_feature_value = 0 if with_labels else 1
-        x = {"f{:03d}".format(feat_num): np.random.randint(lowest_feature_value, 4 + lowest_feature_value) for feat_num
+        x = {"f{:03d}".format(feat_num): np.random.randint(4) for feat_num
              in range(num_feat_files * num_feats_per_file)}
         x = OrderedDict(sorted(x.items(), key=lambda t: t[0]))
         ids.append(ex_id)
@@ -959,7 +953,7 @@ def test_convert_featureset():
     # Test the conversion from every format to every other format
     # with and without labels
     for from_suffix, to_suffix in itertools.permutations(['.jsonlines',
-                                                          '.megam', '.tsv',
+                                                          '.tsv',
                                                           '.csv', '.arff',
                                                           '.libsvm'], 2):
         yield check_convert_featureset, from_suffix, to_suffix, True

pip install -e .
parse_log_pytest
requirements.txt
nose nose-cov pytest
apt-get update apt-get install -y gcc
3.7
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
beautifulsoup4==4.13.3
certifi @ file:///croot/certifi_1671487769961/work/certifi
cov-core==1.15.0
coverage==7.2.7
cycler==0.11.0
exceptiongroup==1.2.2
fonttools==4.38.0
importlib-metadata==6.7.0
iniconfig==2.0.0
joblib==1.3.2
kiwisolver==1.4.5
matplotlib==3.5.3
nose==1.3.7
nose-cov==1.6
numpy==1.21.6
packaging==24.0
pandas==1.3.5
Pillow==9.5.0
pluggy==1.2.0
pyparsing==3.1.4
pytest==7.4.4
python-dateutil==2.9.0.post0
pytz==2025.2
ruamel.yaml==0.18.10
ruamel.yaml.clib==0.2.8
scikit-learn==0.22.2.post1
scipy==1.7.3
seaborn==0.12.2
six==1.17.0
-e git+https://github.com/EducationalTestingService/skll.git@ee5b4a1300f3ab69e2ea14b4b6315fe9e25740c0#egg=skll
soupsieve==2.4.1
tabulate==0.9.0
tomli==2.0.1
typing_extensions==4.7.1
zipp==3.15.0

name: skll
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - beautifulsoup4==4.13.3
      - cov-core==1.15.0
      - coverage==7.2.7
      - cycler==0.11.0
      - exceptiongroup==1.2.2
      - fonttools==4.38.0
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - joblib==1.3.2
      - kiwisolver==1.4.5
      - matplotlib==3.5.3
      - nose==1.3.7
      - nose-cov==1.6
      - numpy==1.21.6
      - packaging==24.0
      - pandas==1.3.5
      - pillow==9.5.0
      - pluggy==1.2.0
      - pyparsing==3.1.4
      - pytest==7.4.4
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - ruamel-yaml==0.18.10
      - ruamel-yaml-clib==0.2.8
      - scikit-learn==0.22.2.post1
      - scipy==1.7.3
      - seaborn==0.12.2
      - six==1.17.0
      - soupsieve==2.4.1
      - tabulate==0.9.0
      - tomli==2.0.1
      - typing-extensions==4.7.1
      - zipp==3.15.0
prefix: /opt/conda/envs/skll

tests/test_commandline_utils.py::test_generate_predictions_console_bad_input_ext
tests/test_commandline_utils.py::test_compute_eval_from_predictions tests/test_commandline_utils.py::test_warning_when_prediction_method_and_no_probabilities tests/test_commandline_utils.py::test_compute_eval_from_predictions_with_probs tests/test_commandline_utils.py::test_compute_eval_from_predictions_breaks_with_expval_and_nonnumeric_classes tests/test_commandline_utils.py::test_conflicting_prediction_and_example_ids tests/test_commandline_utils.py::test_compute_eval_from_predictions_random_choice tests/test_commandline_utils.py::test_generate_predictions_threshold_not_trained_with_probability tests/test_commandline_utils.py::test_generate_predictions_threshold_multi_class tests/test_commandline_utils.py::test_generate_predictions_threshold_non_probabilistic tests/test_commandline_utils.py::test_generate_predictions_predict_labels_not_trained_with_probability tests/test_commandline_utils.py::test_generate_predictions_predict_labels_non_probabilistic tests/test_commandline_utils.py::test_mutually_exclusive_generate_predictions_args tests/test_commandline_utils.py::test_skll_convert_libsvm_map tests/test_commandline_utils.py::test_skll_convert_no_labels_with_label_col tests/test_commandline_utils.py::test_plot_learning_curves_argparse tests/test_commandline_utils.py::test_plot_learning_curves_missing_file tests/test_commandline_utils.py::test_plot_learning_curves_create_output_directory tests/test_commandline_utils.py::test_filter_features_libsvm_input_argparse tests/test_commandline_utils.py::test_filter_features_libsvm_output_argparse tests/test_commandline_utils.py::test_filter_features_unknown_input_format tests/test_commandline_utils.py::test_filter_features_unknown_output_format tests/test_commandline_utils.py::test_join_features_libsvm_input_argparse tests/test_commandline_utils.py::test_join_features_libsvm_output_argparse tests/test_commandline_utils.py::test_join_features_unknown_input_format tests/test_commandline_utils.py::test_join_features_unknown_output_format tests/test_commandline_utils.py::test_filter_features_with_drop_blanks tests/test_commandline_utils.py::test_filter_features_with_replace_blanks_with tests/test_commandline_utils.py::test_filter_features_with_replace_blanks_with_and_drop_blanks_raises_error tests/test_featureset.py::test_empty_ids tests/test_featureset.py::test_empty_labels tests/test_featureset.py::test_length tests/test_featureset.py::test_string_feature tests/test_featureset.py::test_equality tests/test_featureset.py::test_vectorizer_inequality tests/test_featureset.py::test_merge_different_vectorizers tests/test_featureset.py::test_merge_different_hashers tests/test_featureset.py::test_merge_different_labels_same_ids tests/test_featureset.py::test_merge_missing_labels tests/test_featureset.py::test_write_hashed_featureset tests/test_featureset.py::test_subtract tests/test_featureset.py::test_mismatch_ids_features tests/test_featureset.py::test_mismatch_labels_features tests/test_featureset.py::test_iteration_without_dictvectorizer tests/test_featureset.py::test_filter_with_hashing tests/test_featureset.py::test_feature_merging_order_invariance tests/test_featureset.py::test_ids_to_floats tests/test_featureset.py::test_dict_list_reader tests/test_featureset.py::test_featureset_creation_from_dataframe_with_labels tests/test_featureset.py::test_featureset_creation_from_dataframe_without_labels tests/test_featureset.py::test_featureset_creation_from_dataframe_with_labels_and_vectorizer tests/test_featureset.py::test_featureset_creation_from_dataframe_without_labels_with_vectorizer tests/test_featureset.py::test_writing_ndj_featureset_with_string_ids tests/test_featureset.py::test_featureset_creation_from_dataframe_with_string_ids tests/test_featureset.py::test_featureset_creation_from_dataframe_with_string_labels tests/test_featureset.py::test_reading_csv_and_tsv_with_drop_blanks tests/test_featureset.py::test_reading_csv_and_tsv_with_fill_blanks tests/test_featureset.py::test_reading_csv_and_tsv_with_fill_blanks_with_dictionary tests/test_featureset.py::test_drop_blanks_and_replace_blanks_with_raises_error
README.rst doc/utilities.rst doc/run_experiment.rst doc/api/quickstart.rst skll/utils/commandline/filter_features.py skll/utils/commandline/generate_predictions.py skll/data/writers.py skll/data/__init__.py skll/utils/commandline/join_features.py skll/data/readers.py doc/tutorial.rst skll/utils/commandline/skll_convert.py
README.rst doc/utilities.rst doc/run_experiment.rst doc/api/quickstart.rst skll/utils/commandline/filter_features.py skll/utils/commandline/generate_predictions.py skll/data/writers.py skll/data/__init__.py skll/utils/commandline/join_features.py skll/data/readers.py doc/tutorial.rst skll/utils/commandline/skll_convert.py