Utility function for selecting columns of only valid feature types.

    Parameters
    ----------
    dataset: SFrame
        The input SFrame containing columns of potential features.

    features: list[str]
        List of feature column names.  If None, the candidate feature set is
        taken to be all the columns in the dataset.

    valid_feature_types: list[type]
        List of Python types that represent valid features.  If type is array.array,
        then an extra check is done to ensure that the individual elements of the array
        are of numeric type.  If type is dict, then an extra check is done to ensure
        that dictionary values are numeric.

    target_column: str
        Name of the target column.  If not None, the target column is excluded
        from the list of valid feature columns.

    Returns
    -------
    out: list[str]
        List of valid feature column names.  Warnings are given for each candidate
        feature column that is excluded.

    Examples
    --------
    # Select all the columns of type `str` in sf, excluding the target column named
    # 'rating'
    >>> valid_columns = _select_valid_features(sf, None, [str], target_column='rating')

    # Select the subset of columns 'X1', 'X2', 'X3' that has dictionary type or defines
    # numeric array type
    >>> valid_columns = _select_valid_features(sf, ['X1', 'X2', 'X3'], [dict, array.array])

def _select_valid_features(dataset, features, valid_feature_types,
                           target_column=None):
    """
    Utility function for selecting columns of only valid feature types.

    Parameters
    ----------
    dataset: SFrame
        The input SFrame containing columns of potential features.

    features: list[str]
        List of feature column names.  If None, the candidate feature set is
        taken to be all the columns in the dataset.

    valid_feature_types: list[type]
        List of Python types that represent valid features.  If type is array.array,
        then an extra check is done to ensure that the individual elements of the array
        are of numeric type.  If type is dict, then an extra check is done to ensure
        that dictionary values are numeric.

    target_column: str
        Name of the target column.  If not None, the target column is excluded
        from the list of valid feature columns.

    Returns
    -------
    out: list[str]
        List of valid feature column names.  Warnings are given for each candidate
        feature column that is excluded.

    Examples
    --------
    # Select all the columns of type `str` in sf, excluding the target column named
    # 'rating'
    >>> valid_columns = _select_valid_features(sf, None, [str], target_column='rating')

    # Select the subset of columns 'X1', 'X2', 'X3' that has dictionary type or defines
    # numeric array type
    >>> valid_columns = _select_valid_features(sf, ['X1', 'X2', 'X3'], [dict, array.array])
    """
    if features is not None:
        if not hasattr(features, '__iter__'):
            raise TypeError("Input 'features' must be an iterable type.")

        if not all([isinstance(x, str) for x in features]):
            raise TypeError("Input 'features' must contain only strings.")

    ## Extract the features and labels
    if features is None:
        features = dataset.column_names()

    col_type_map = {
        col_name: col_type for (col_name, col_type) in
        zip(dataset.column_names(), dataset.column_types())}

    valid_features = []
    for col_name in features:

        if col_name not in dataset.column_names():
            _logging.warning("Column '{}' is not in the input dataset.".format(col_name))

        elif col_name == target_column:
            _logging.warning("Excluding target column " + target_column + " as a feature.")

        elif col_type_map[col_name] not in valid_feature_types:
            _logging.warning("Column '{}' is excluded as a ".format(col_name) +
                             "feature due to invalid column type.")

        else:
            valid_features.append(col_name)

    if len(valid_features) == 0:
        raise ValueError("The dataset does not contain any valid feature columns. " +
                         "Accepted feature types are " + str(valid_feature_types) + ".")

    return valid_features