Concatenate values from one or two columns into one column, grouping by
        all other columns. The resulting column could be of type list, array or
        dictionary.  If ``column_names`` is a numeric column, the result will be of
        array.array type.  If ``column_names`` is a non-numeric column, the new column
        will be of list type. If ``column_names`` is a list of two columns, the new
        column will be of dict type where the keys are taken from the first
        column in the list.

        Parameters
        ----------
        column_names : str | [str, str]
            The column(s) that is(are) to be concatenated.
            If str, then collapsed column type is either array or list.
            If [str, str], then collapsed column type is dict

        new_column_name : str, optional
            New column name. If not given, a name is generated automatically.

        Returns
        -------
        out : SFrame
            A new SFrame containing the grouped columns as well as the new
            column.

        See Also
        --------
        stack : The inverse of unstack.

        groupby : ``unstack`` is a special version of ``groupby`` that uses the
          :mod:`~turicreate.aggregate.CONCAT` aggregator

        Notes
        -----
        - There is no guarantee the resulting SFrame maintains the same order as
          the original SFrame.

        - Missing values are maintained during unstack.

        - When unstacking into a dictionary, if there is more than one instance
          of a given key for a particular group, an arbitrary value is selected.

        Examples
        --------
        >>> sf = turicreate.SFrame({'count':[4, 2, 1, 1, 2, None],
        ...                       'topic':['cat', 'cat', 'dog', 'elephant', 'elephant', 'fish'],
        ...                       'word':['a', 'c', 'c', 'a', 'b', None]})
        >>> sf.unstack(column_names=['word', 'count'], new_column_name='words')
        +----------+------------------+
        |  topic   |      words       |
        +----------+------------------+
        | elephant | {'a': 1, 'b': 2} |
        |   dog    |     {'c': 1}     |
        |   cat    | {'a': 4, 'c': 2} |
        |   fish   |       None       |
        +----------+------------------+
        [4 rows x 2 columns]

        >>> sf = turicreate.SFrame({'friend': [2, 3, 4, 5, 6, 4, 5, 2, 3],
        ...                      'user': [1, 1, 1, 2, 2, 2, 3, 4, 4]})
        >>> sf.unstack('friend', new_column_name='new name')
        +------+-----------+
        | user |  new name |
        +------+-----------+
        |  3   |    [5]    |
        |  1   | [2, 3, 4] |
        |  2   | [6, 4, 5] |
        |  4   |   [2, 3]  |
        +------+-----------+
        [4 rows x 2 columns]

def unstack(self, column_names, new_column_name=None):
        """
        Concatenate values from one or two columns into one column, grouping by
        all other columns. The resulting column could be of type list, array or
        dictionary.  If ``column_names`` is a numeric column, the result will be of
        array.array type.  If ``column_names`` is a non-numeric column, the new column
        will be of list type. If ``column_names`` is a list of two columns, the new
        column will be of dict type where the keys are taken from the first
        column in the list.

        Parameters
        ----------
        column_names : str | [str, str]
            The column(s) that is(are) to be concatenated.
            If str, then collapsed column type is either array or list.
            If [str, str], then collapsed column type is dict

        new_column_name : str, optional
            New column name. If not given, a name is generated automatically.

        Returns
        -------
        out : SFrame
            A new SFrame containing the grouped columns as well as the new
            column.

        See Also
        --------
        stack : The inverse of unstack.

        groupby : ``unstack`` is a special version of ``groupby`` that uses the
          :mod:`~turicreate.aggregate.CONCAT` aggregator

        Notes
        -----
        - There is no guarantee the resulting SFrame maintains the same order as
          the original SFrame.

        - Missing values are maintained during unstack.

        - When unstacking into a dictionary, if there is more than one instance
          of a given key for a particular group, an arbitrary value is selected.

        Examples
        --------
        >>> sf = turicreate.SFrame({'count':[4, 2, 1, 1, 2, None],
        ...                       'topic':['cat', 'cat', 'dog', 'elephant', 'elephant', 'fish'],
        ...                       'word':['a', 'c', 'c', 'a', 'b', None]})
        >>> sf.unstack(column_names=['word', 'count'], new_column_name='words')
        +----------+------------------+
        |  topic   |      words       |
        +----------+------------------+
        | elephant | {'a': 1, 'b': 2} |
        |   dog    |     {'c': 1}     |
        |   cat    | {'a': 4, 'c': 2} |
        |   fish   |       None       |
        +----------+------------------+
        [4 rows x 2 columns]

        >>> sf = turicreate.SFrame({'friend': [2, 3, 4, 5, 6, 4, 5, 2, 3],
        ...                      'user': [1, 1, 1, 2, 2, 2, 3, 4, 4]})
        >>> sf.unstack('friend', new_column_name='new name')
        +------+-----------+
        | user |  new name |
        +------+-----------+
        |  3   |    [5]    |
        |  1   | [2, 3, 4] |
        |  2   | [6, 4, 5] |
        |  4   |   [2, 3]  |
        +------+-----------+
        [4 rows x 2 columns]
        """
        if (type(column_names) != str and len(column_names) != 2):
            raise TypeError("'column_names' parameter has to be either a string or a list of two strings.")

        with cython_context():
            if type(column_names) == str:
                key_columns = [i for i in self.column_names() if i != column_names]
                if new_column_name is not None:
                    return self.groupby(key_columns, {new_column_name : aggregate.CONCAT(column_names)})
                else:
                    return self.groupby(key_columns, aggregate.CONCAT(column_names))
            elif len(column_names) == 2:
                key_columns = [i for i in self.column_names() if i not in column_names]
                if new_column_name is not None:
                    return self.groupby(key_columns, {new_column_name: aggregate.CONCAT(column_names[0], column_names[1])})
                else:
                    return self.groupby(key_columns, aggregate.CONCAT(column_names[0], column_names[1]))