# Buggy code
def fetch_20newsgroups(
    data_home=None,
    subset="train",
    categories=None,
    shuffle=True,
    random_state=42,
    remove=(),
    download_if_missing=True,
):
    """Load the filenames and data from the 20 newsgroups dataset.

    Parameters
    ----------
    subset: 'train' or 'test', 'all', optional
        Select the dataset to load: 'train' for the training set, 'test'
        for the test set, 'all' for both, with shuffled ordering.

    data_home: optional, default: None
        Specify an download and cache folder for the datasets. If None,
        all scikit-learn data is stored in '~/scikit_learn_data' subfolders.

    categories: None or collection of string or unicode
        If None (default), load all the categories.
        If not None, list of category names to load (other categories
        ignored).

    shuffle: bool, optional
        Whether or not to shuffle the data: might be important for models that
        make the assumption that the samples are independent and identically
        distributed (i.i.d.), such as stochastic gradient descent.

    random_state: numpy random number generator or seed integer
        Used to shuffle the dataset.

    download_if_missing: optional, True by default
        If False, raise an IOError if the data is not locally available
        instead of trying to download the data from the source site.

    remove: tuple
        May contain any subset of ('headers', 'footers', 'quotes'). Each of
        these are kinds of text that will be detected and removed from the
        newsgroup posts, preventing classifiers from overfitting on
        metadata.

        'headers' removes newsgroup headers, 'footers' removes blocks at the
        ends of posts that look like signatures, and 'quotes' removes lines
        that appear to be quoting another post.

        'headers' follows an exact standard; the other filters are not always
        correct.
    """

    data_home = get_data_home(data_home=data_home)
    cache_path = os.path.join(data_home, CACHE_NAME)
    twenty_home = os.path.join(data_home, "20news_home")
    cache = None
    if os.path.exists(cache_path):
        try:
            cache = pickle.loads(open(cache_path, "rb").read().decode("zip"))
        except Exception as e:
            print(80 * "_")
            print("Cache loading failed")
            print(80 * "_")
            print(e)

    if cache is None:
        if download_if_missing:
            cache = download_20newsgroups(target_dir=twenty_home, cache_path=cache_path)
        else:
            raise IOError("20Newsgroups dataset not found")

    if subset in ("train", "test"):
        data = cache[subset]
    elif subset == "all":
        data_lst = list()
        target = list()
        filenames = list()
        for subset in ("train", "test"):
            data = cache[subset]
            data_lst.extend(data.data)
            target.extend(data.target)
            filenames.extend(data.filenames)

        data.data = data_lst
        data.target = np.array(target)
        data.filenames = np.array(filenames)
        data.description = "the 20 newsgroups by date dataset"
    else:
        raise ValueError(
            "subset can only be 'train', 'test' or 'all', got '%s'" % subset
        )

    if "headers" in remove:
        data.data = [strip_newsgroup_header(text) for text in data.data]
    if "footers" in remove:
        data.data = [strip_newsgroup_footer(text) for text in data.data]
    if "quotes" in remove:
        data.data = [strip_newsgroup_quoting(text) for text in data.data]

    if categories is not None:
        labels = [(data.target_names.index(cat), cat) for cat in categories]
        # Sort the categories to have the ordering of the labels
        labels.sort()
        labels, categories = zip(*labels)
        mask = in1d(data.target, labels)
        data.filenames = data.filenames[mask]
        data.target = data.target[mask]
        # searchsorted to have continuous labels
        data.target = np.searchsorted(labels, data.target)
        data.target_names = list(categories)
        # Use an object array to shuffle: avoids memory copy
        data_lst = np.array(data.data, dtype=object)
        data_lst = data_lst[mask]
        data.data = data_lst.tolist()

    if shuffle:
        random_state = check_random_state(random_state)
        indices = np.arange(data.target.shape[0])
        random_state.shuffle(indices)
        data.filenames = data.filenames[indices]
        data.target = data.target[indices]
        # Use an object array to shuffle: avoids memory copy
        data_lst = np.array(data.data, dtype=object)
        data_lst = data_lst[indices]
        data.data = data_lst.tolist()

    return data


# Traceback (AttributeError)
Downloading dataset from http://people.csail.mit.edu/jrennie/20Newsgroups/20news-bydate.tar.gz (14 MB)
Traceback (most recent call last):
File "/Users/hop/VirtualEnvs/data_vis/lib/python3.3/site-packages/IPython/core/interactiveshell.py", line 2828, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-42-bf8e77d5e59e>", line 1, in <module>
news = fetch_20newsgroups(subset='all')
File "/Users/hop/VirtualEnvs/data_vis/lib/python3.3/site-packages/sklearn/datasets/twenty_newsgroups.py", line 207, in fetch_20newsgroups
cache_path=cache_path)
File "/Users/hop/VirtualEnvs/data_vis/lib/python3.3/site-packages/sklearn/datasets/twenty_newsgroups.py", line 95, in download_20newsgroups
open(cache_path, 'wb').write(pickle.dumps(cache).encode('zip'))
AttributeError: 'bytes' object has no attribute 'encode'

# Fixed code
def fetch_20newsgroups(
    data_home=None,
    subset="train",
    categories=None,
    shuffle=True,
    random_state=42,
    remove=(),
    download_if_missing=True,
):
    """Load the filenames and data from the 20 newsgroups dataset.

    Parameters
    ----------
    subset: 'train' or 'test', 'all', optional
        Select the dataset to load: 'train' for the training set, 'test'
        for the test set, 'all' for both, with shuffled ordering.

    data_home: optional, default: None
        Specify an download and cache folder for the datasets. If None,
        all scikit-learn data is stored in '~/scikit_learn_data' subfolders.

    categories: None or collection of string or unicode
        If None (default), load all the categories.
        If not None, list of category names to load (other categories
        ignored).

    shuffle: bool, optional
        Whether or not to shuffle the data: might be important for models that
        make the assumption that the samples are independent and identically
        distributed (i.i.d.), such as stochastic gradient descent.

    random_state: numpy random number generator or seed integer
        Used to shuffle the dataset.

    download_if_missing: optional, True by default
        If False, raise an IOError if the data is not locally available
        instead of trying to download the data from the source site.

    remove: tuple
        May contain any subset of ('headers', 'footers', 'quotes'). Each of
        these are kinds of text that will be detected and removed from the
        newsgroup posts, preventing classifiers from overfitting on
        metadata.

        'headers' removes newsgroup headers, 'footers' removes blocks at the
        ends of posts that look like signatures, and 'quotes' removes lines
        that appear to be quoting another post.

        'headers' follows an exact standard; the other filters are not always
        correct.
    """

    data_home = get_data_home(data_home=data_home)
    cache_path = os.path.join(data_home, CACHE_NAME)
    twenty_home = os.path.join(data_home, "20news_home")
    cache = None
    if os.path.exists(cache_path):
        try:
            compressed_content = open(cache_path, "rb").read()
            uncompressed_content = codecs.decode(compressed_content, "zlib_codec")
            cache = pickle.loads(uncompressed_content)
        except Exception as e:
            print(80 * "_")
            print("Cache loading failed")
            print(80 * "_")
            print(e)

    if cache is None:
        if download_if_missing:
            cache = download_20newsgroups(target_dir=twenty_home, cache_path=cache_path)
        else:
            raise IOError("20Newsgroups dataset not found")

    if subset in ("train", "test"):
        data = cache[subset]
    elif subset == "all":
        data_lst = list()
        target = list()
        filenames = list()
        for subset in ("train", "test"):
            data = cache[subset]
            data_lst.extend(data.data)
            target.extend(data.target)
            filenames.extend(data.filenames)

        data.data = data_lst
        data.target = np.array(target)
        data.filenames = np.array(filenames)
        data.description = "the 20 newsgroups by date dataset"
    else:
        raise ValueError(
            "subset can only be 'train', 'test' or 'all', got '%s'" % subset
        )

    if "headers" in remove:
        data.data = [strip_newsgroup_header(text) for text in data.data]
    if "footers" in remove:
        data.data = [strip_newsgroup_footer(text) for text in data.data]
    if "quotes" in remove:
        data.data = [strip_newsgroup_quoting(text) for text in data.data]

    if categories is not None:
        labels = [(data.target_names.index(cat), cat) for cat in categories]
        # Sort the categories to have the ordering of the labels
        labels.sort()
        labels, categories = zip(*labels)
        mask = in1d(data.target, labels)
        data.filenames = data.filenames[mask]
        data.target = data.target[mask]
        # searchsorted to have continuous labels
        data.target = np.searchsorted(labels, data.target)
        data.target_names = list(categories)
        # Use an object array to shuffle: avoids memory copy
        data_lst = np.array(data.data, dtype=object)
        data_lst = data_lst[mask]
        data.data = data_lst.tolist()

    if shuffle:
        random_state = check_random_state(random_state)
        indices = np.arange(data.target.shape[0])
        random_state.shuffle(indices)
        data.filenames = data.filenames[indices]
        data.target = data.target[indices]
        # Use an object array to shuffle: avoids memory copy
        data_lst = np.array(data.data, dtype=object)
        data_lst = data_lst[indices]
        data.data = data_lst.tolist()

    return data