def merge(self, right: 'DataFrame', how: str='inner', on: Optional[Union[(Name, List[Name])]]=None, left_on: Optional[Union[(Name, List[Name])]]=None, right_on: Optional[Union[(Name, List[Name])]]=None, left_index: bool=False, right_index: bool=False, suffixes: Tuple[(str, str)]=('_x', '_y')) -> 'DataFrame':
    "\n        Merge DataFrame objects with a database-style join.\n\n        The index of the resulting DataFrame will be one of the following:\n            - 0...n if no index is used for merging\n            - Index of the left DataFrame if merged only on the index of the right DataFrame\n            - Index of the right DataFrame if merged only on the index of the left DataFrame\n            - All involved indices if merged using the indices of both DataFrames\n                e.g. if `left` with indices (a, x) and `right` with indices (b, x), the result will\n                be an index (x, a, b)\n\n        Parameters\n        ----------\n        right: Object to merge with.\n        how: Type of merge to be performed.\n            {'left', 'right', 'outer', 'inner'}, default 'inner'\n\n            left: use only keys from left frame, similar to a SQL left outer join; not preserve\n                key order unlike pandas.\n            right: use only keys from right frame, similar to a SQL right outer join; not preserve\n                key order unlike pandas.\n            outer: use union of keys from both frames, similar to a SQL full outer join; sort keys\n                lexicographically.\n            inner: use intersection of keys from both frames, similar to a SQL inner join;\n                not preserve the order of the left keys unlike pandas.\n        on: Column or index level names to join on. These must be found in both DataFrames. If on\n            is None and not merging on indexes then this defaults to the intersection of the\n            columns in both DataFrames.\n        left_on: Column or index level names to join on in the left DataFrame. Can also\n            be an array or list of arrays of the length of the left DataFrame.\n            These arrays are treated as if they are columns.\n        right_on: Column or index level names to join on in the right DataFrame. Can also\n            be an array or list of arrays of the length of the right DataFrame.\n            These arrays are treated as if they are columns.\n        left_index: Use the index from the left DataFrame as the join key(s). If it is a\n            MultiIndex, the number of keys in the other DataFrame (either the index or a number of\n            columns) must match the number of levels.\n        right_index: Use the index from the right DataFrame as the join key. Same caveats as\n            left_index.\n        suffixes: Suffix to apply to overlapping column names in the left and right side,\n            respectively.\n\n        Returns\n        -------\n        DataFrame\n            A DataFrame of the two merged objects.\n\n        See Also\n        --------\n        DataFrame.join : Join columns of another DataFrame.\n        DataFrame.update : Modify in place using non-NA values from another DataFrame.\n        DataFrame.hint : Specifies some hint on the current DataFrame.\n        broadcast : Marks a DataFrame as small enough for use in broadcast joins.\n\n        Examples\n        --------\n        >>> df1 = ps.DataFrame({'lkey': ['foo', 'bar', 'baz', 'foo'],\n        ...                     'value': [1, 2, 3, 5]},\n        ...                    columns=['lkey', 'value'])\n        >>> df2 = ps.DataFrame({'rkey': ['foo', 'bar', 'baz', 'foo'],\n        ...                     'value': [5, 6, 7, 8]},\n        ...                    columns=['rkey', 'value'])\n        >>> df1\n          lkey  value\n        0  foo      1\n        1  bar      2\n        2  baz      3\n        3  foo      5\n        >>> df2\n          rkey  value\n        0  foo      5\n        1  bar      6\n        2  baz      7\n        3  foo      8\n\n        Merge df1 and df2 on the lkey and rkey columns. The value columns have\n        the default suffixes, _x and _y, appended.\n\n        >>> merged = df1.merge(df2, left_on='lkey', right_on='rkey')\n        >>> merged.sort_values(by=['lkey', 'value_x', 'rkey', 'value_y'])  # doctest: +ELLIPSIS\n          lkey  value_x rkey  value_y\n        ...bar        2  bar        6\n        ...baz        3  baz        7\n        ...foo        1  foo        5\n        ...foo        1  foo        8\n        ...foo        5  foo        5\n        ...foo        5  foo        8\n\n        >>> left_psdf = ps.DataFrame({'A': [1, 2]})\n        >>> right_psdf = ps.DataFrame({'B': ['x', 'y']}, index=[1, 2])\n\n        >>> left_psdf.merge(right_psdf, left_index=True, right_index=True).sort_index()\n           A  B\n        1  2  x\n\n        >>> left_psdf.merge(right_psdf, left_index=True, right_index=True, how='left').sort_index()\n           A     B\n        0  1  None\n        1  2     x\n\n        >>> left_psdf.merge(right_psdf, left_index=True, right_index=True, how='right').sort_index()\n             A  B\n        1  2.0  x\n        2  NaN  y\n\n        >>> left_psdf.merge(right_psdf, left_index=True, right_index=True, how='outer').sort_index()\n             A     B\n        0  1.0  None\n        1  2.0     x\n        2  NaN     y\n\n        Notes\n        -----\n        As described in #263, joining string columns currently returns None for missing values\n            instead of NaN.\n        "

    def to_list(os: Optional[Union[(Name, List[Name])]]) -> List[Label]:
        if (os is None):
            return []
        elif is_name_like_tuple(os):
            return [cast(Label, os)]
        elif is_name_like_value(os):
            return [(os,)]
        else:
            return [(o if is_name_like_tuple(o) else (o,)) for o in os]
    if isinstance(right, ps.Series):
        right = right.to_frame()
    if on:
        if (left_on or right_on):
            raise ValueError('Can only pass argument "on" OR "left_on" and "right_on", not a combination of both.')
        left_key_names = list(map(self._internal.spark_column_name_for, to_list(on)))
        right_key_names = list(map(right._internal.spark_column_name_for, to_list(on)))
    else:
        if left_index:
            left_key_names = self._internal.index_spark_column_names
        else:
            left_key_names = list(map(self._internal.spark_column_name_for, to_list(left_on)))
        if right_index:
            right_key_names = right._internal.index_spark_column_names
        else:
            right_key_names = list(map(right._internal.spark_column_name_for, to_list(right_on)))
        if (left_key_names and (not right_key_names)):
            raise ValueError('Must pass right_on or right_index=True')
        if (right_key_names and (not left_key_names)):
            raise ValueError('Must pass left_on or left_index=True')
        if ((not left_key_names) and (not right_key_names)):
            common = list(self.columns.intersection(right.columns))
            if (len(common) == 0):
                raise ValueError('No common columns to perform merge on. Merge options: left_on=None, right_on=None, left_index=False, right_index=False')
            left_key_names = list(map(self._internal.spark_column_name_for, to_list(common)))
            right_key_names = list(map(right._internal.spark_column_name_for, to_list(common)))
        if (len(left_key_names) != len(right_key_names)):
            raise ValueError('len(left_keys) must equal len(right_keys)')
    right_prefix = '__right_'
    right_key_names = [(right_prefix + right_key_name) for right_key_name in right_key_names]
    how = validate_how(how)

    def resolve(internal: InternalFrame, side: str) -> InternalFrame:

        def rename(col: str) -> str:
            return '__{}_{}'.format(side, col)
        internal = internal.resolved_copy
        sdf = internal.spark_frame
        sdf = sdf.select(*[scol_for(sdf, col).alias(rename(col)) for col in sdf.columns if (col not in HIDDEN_COLUMNS)], *HIDDEN_COLUMNS)
        return internal.copy(spark_frame=sdf, index_spark_columns=[scol_for(sdf, rename(col)) for col in internal.index_spark_column_names], index_fields=[field.copy(name=rename(field.name)) for field in internal.index_fields], data_spark_columns=[scol_for(sdf, rename(col)) for col in internal.data_spark_column_names], data_fields=[field.copy(name=rename(field.name)) for field in internal.data_fields])
    left_internal = self._internal.resolved_copy
    right_internal = resolve(right._internal, 'right')
    left_table = left_internal.spark_frame.alias('left_table')
    right_table = right_internal.spark_frame.alias('right_table')
    left_key_columns = [scol_for(left_table, label) for label in left_key_names]
    right_key_columns = [scol_for(right_table, label) for label in right_key_names]
    join_condition = reduce((lambda x, y: (x & y)), [(lkey == rkey) for (lkey, rkey) in zip(left_key_columns, right_key_columns)])
    joined_table = left_table.join(right_table, join_condition, how=how)
    left_suffix = suffixes[0]
    right_suffix = suffixes[1]
    duplicate_columns = (set(left_internal.column_labels) & set(right_internal.column_labels))
    exprs = []
    data_columns = []
    column_labels = []

    def left_scol_for(label: Label) -> Column:
        return scol_for(left_table, left_internal.spark_column_name_for(label))

    def right_scol_for(label: Label) -> Column:
        return scol_for(right_table, right_internal.spark_column_name_for(label))
    for label in left_internal.column_labels:
        col = left_internal.spark_column_name_for(label)
        scol = left_scol_for(label)
        if (label in duplicate_columns):
            spark_column_name = left_internal.spark_column_name_for(label)
            if ((spark_column_name in left_key_names) and ((right_prefix + spark_column_name) in right_key_names)):
                right_scol = right_scol_for(label)
                if (how == 'right'):
                    scol = right_scol.alias(col)
                elif (how == 'full'):
                    scol = F.when(scol.isNotNull(), scol).otherwise(right_scol).alias(col)
                else:
                    pass
            else:
                col = (col + left_suffix)
                scol = scol.alias(col)
                label = tuple(([(str(label[0]) + left_suffix)] + list(label[1:])))
        exprs.append(scol)
        data_columns.append(col)
        column_labels.append(label)
    for label in right_internal.column_labels:
        col = right_internal.spark_column_name_for(label)[len(right_prefix):]
        scol = right_scol_for(label).alias(col)
        if (label in duplicate_columns):
            spark_column_name = left_internal.spark_column_name_for(label)
            if ((spark_column_name in left_key_names) and ((right_prefix + spark_column_name) in right_key_names)):
                continue
            else:
                col = (col + right_suffix)
                scol = scol.alias(col)
                label = tuple(([(str(label[0]) + right_suffix)] + list(label[1:])))
        exprs.append(scol)
        data_columns.append(col)
        column_labels.append(label)
    left_index_scols = left_internal.index_spark_columns
    right_index_scols = right_internal.index_spark_columns
    if left_index:
        if right_index:
            if (how in ('inner', 'left')):
                exprs.extend(left_index_scols)
                index_spark_column_names = left_internal.index_spark_column_names
                index_names = left_internal.index_names
            elif (how == 'right'):
                exprs.extend(right_index_scols)
                index_spark_column_names = right_internal.index_spark_column_names
                index_names = right_internal.index_names
            else:
                index_spark_column_names = left_internal.index_spark_column_names
                index_names = left_internal.index_names
                for (col, left_scol, right_scol) in zip(index_spark_column_names, left_index_scols, right_index_scols):
                    scol = F.when(left_scol.isNotNull(), left_scol).otherwise(right_scol)
                    exprs.append(scol.alias(col))
        else:
            exprs.extend(right_index_scols)
            index_spark_column_names = right_internal.index_spark_column_names
            index_names = right_internal.index_names
    elif right_index:
        exprs.extend(left_index_scols)
        index_spark_column_names = left_internal.index_spark_column_names
        index_names = left_internal.index_names
    else:
        index_spark_column_names = []
        index_names = []
    selected_columns = joined_table.select(*exprs)
    internal = InternalFrame(spark_frame=selected_columns, index_spark_columns=[scol_for(selected_columns, col) for col in index_spark_column_names], index_names=index_names, column_labels=column_labels, data_spark_columns=[scol_for(selected_columns, col) for col in data_columns])
    return DataFrame(internal)
Merge DataFrame objects with a database-style join.

The index of the resulting DataFrame will be one of the following:
    - 0...n if no index is used for merging
    - Index of the left DataFrame if merged only on the index of the right DataFrame
    - Index of the right DataFrame if merged only on the index of the left DataFrame
    - All involved indices if merged using the indices of both DataFrames
        e.g. if `left` with indices (a, x) and `right` with indices (b, x), the result will
        be an index (x, a, b)

Parameters
----------
right: Object to merge with.
how: Type of merge to be performed.
    {'left', 'right', 'outer', 'inner'}, default 'inner'

    left: use only keys from left frame, similar to a SQL left outer join; not preserve
        key order unlike pandas.
    right: use only keys from right frame, similar to a SQL right outer join; not preserve
        key order unlike pandas.
    outer: use union of keys from both frames, similar to a SQL full outer join; sort keys
        lexicographically.
    inner: use intersection of keys from both frames, similar to a SQL inner join;
        not preserve the order of the left keys unlike pandas.
on: Column or index level names to join on. These must be found in both DataFrames. If on
    is None and not merging on indexes then this defaults to the intersection of the
    columns in both DataFrames.
left_on: Column or index level names to join on in the left DataFrame. Can also
    be an array or list of arrays of the length of the left DataFrame.
    These arrays are treated as if they are columns.
right_on: Column or index level names to join on in the right DataFrame. Can also
    be an array or list of arrays of the length of the right DataFrame.
    These arrays are treated as if they are columns.
left_index: Use the index from the left DataFrame as the join key(s). If it is a
    MultiIndex, the number of keys in the other DataFrame (either the index or a number of
    columns) must match the number of levels.
right_index: Use the index from the right DataFrame as the join key. Same caveats as
    left_index.
suffixes: Suffix to apply to overlapping column names in the left and right side,
    respectively.

Returns
-------
DataFrame
    A DataFrame of the two merged objects.

See Also
--------
DataFrame.join : Join columns of another DataFrame.
DataFrame.update : Modify in place using non-NA values from another DataFrame.
DataFrame.hint : Specifies some hint on the current DataFrame.
broadcast : Marks a DataFrame as small enough for use in broadcast joins.

Examples
--------
>>> df1 = ps.DataFrame({'lkey': ['foo', 'bar', 'baz', 'foo'],
...                     'value': [1, 2, 3, 5]},
...                    columns=['lkey', 'value'])
>>> df2 = ps.DataFrame({'rkey': ['foo', 'bar', 'baz', 'foo'],
...                     'value': [5, 6, 7, 8]},
...                    columns=['rkey', 'value'])
>>> df1
  lkey  value
0  foo      1
1  bar      2
2  baz      3
3  foo      5
>>> df2
  rkey  value
0  foo      5
1  bar      6
2  baz      7
3  foo      8

Merge df1 and df2 on the lkey and rkey columns. The value columns have
the default suffixes, _x and _y, appended.

>>> merged = df1.merge(df2, left_on='lkey', right_on='rkey')
>>> merged.sort_values(by=['lkey', 'value_x', 'rkey', 'value_y'])  # doctest: +ELLIPSIS
  lkey  value_x rkey  value_y
...bar        2  bar        6
...baz        3  baz        7
...foo        1  foo        5
...foo        1  foo        8
...foo        5  foo        5
...foo        5  foo        8

>>> left_psdf = ps.DataFrame({'A': [1, 2]})
>>> right_psdf = ps.DataFrame({'B': ['x', 'y']}, index=[1, 2])

>>> left_psdf.merge(right_psdf, left_index=True, right_index=True).sort_index()
   A  B
1  2  x

>>> left_psdf.merge(right_psdf, left_index=True, right_index=True, how='left').sort_index()
   A     B
0  1  None
1  2     x

>>> left_psdf.merge(right_psdf, left_index=True, right_index=True, how='right').sort_index()
     A  B
1  2.0  x
2  NaN  y

>>> left_psdf.merge(right_psdf, left_index=True, right_index=True, how='outer').sort_index()
     A     B
0  1.0  None
1  2.0     x
2  NaN     y

Notes
-----
As described in #263, joining string columns currently returns None for missing values
    instead of NaN.
def merge(self, right: 'DataFrame', how: str='inner', on: Optional[Union[(Name, List[Name])]]=None, left_on: Optional[Union[(Name, List[Name])]]=None, right_on: Optional[Union[(Name, List[Name])]]=None, left_index: bool=False, right_index: bool=False, suffixes: Tuple[(str, str)]=('_x', '_y')) -> 'DataFrame':
    "\n        Merge DataFrame objects with a database-style join.\n\n        The index of the resulting DataFrame will be one of the following:\n            - 0...n if no index is used for merging\n            - Index of the left DataFrame if merged only on the index of the right DataFrame\n            - Index of the right DataFrame if merged only on the index of the left DataFrame\n            - All involved indices if merged using the indices of both DataFrames\n                e.g. if `left` with indices (a, x) and `right` with indices (b, x), the result will\n                be an index (x, a, b)\n\n        Parameters\n        ----------\n        right: Object to merge with.\n        how: Type of merge to be performed.\n            {'left', 'right', 'outer', 'inner'}, default 'inner'\n\n            left: use only keys from left frame, similar to a SQL left outer join; not preserve\n                key order unlike pandas.\n            right: use only keys from right frame, similar to a SQL right outer join; not preserve\n                key order unlike pandas.\n            outer: use union of keys from both frames, similar to a SQL full outer join; sort keys\n                lexicographically.\n            inner: use intersection of keys from both frames, similar to a SQL inner join;\n                not preserve the order of the left keys unlike pandas.\n        on: Column or index level names to join on. These must be found in both DataFrames. If on\n            is None and not merging on indexes then this defaults to the intersection of the\n            columns in both DataFrames.\n        left_on: Column or index level names to join on in the left DataFrame. Can also\n            be an array or list of arrays of the length of the left DataFrame.\n            These arrays are treated as if they are columns.\n        right_on: Column or index level names to join on in the right DataFrame. Can also\n            be an array or list of arrays of the length of the right DataFrame.\n            These arrays are treated as if they are columns.\n        left_index: Use the index from the left DataFrame as the join key(s). If it is a\n            MultiIndex, the number of keys in the other DataFrame (either the index or a number of\n            columns) must match the number of levels.\n        right_index: Use the index from the right DataFrame as the join key. Same caveats as\n            left_index.\n        suffixes: Suffix to apply to overlapping column names in the left and right side,\n            respectively.\n\n        Returns\n        -------\n        DataFrame\n            A DataFrame of the two merged objects.\n\n        See Also\n        --------\n        DataFrame.join : Join columns of another DataFrame.\n        DataFrame.update : Modify in place using non-NA values from another DataFrame.\n        DataFrame.hint : Specifies some hint on the current DataFrame.\n        broadcast : Marks a DataFrame as small enough for use in broadcast joins.\n\n        Examples\n        --------\n        >>> df1 = ps.DataFrame({'lkey': ['foo', 'bar', 'baz', 'foo'],\n        ...                     'value': [1, 2, 3, 5]},\n        ...                    columns=['lkey', 'value'])\n        >>> df2 = ps.DataFrame({'rkey': ['foo', 'bar', 'baz', 'foo'],\n        ...                     'value': [5, 6, 7, 8]},\n        ...                    columns=['rkey', 'value'])\n        >>> df1\n          lkey  value\n        0  foo      1\n        1  bar      2\n        2  baz      3\n        3  foo      5\n        >>> df2\n          rkey  value\n        0  foo      5\n        1  bar      6\n        2  baz      7\n        3  foo      8\n\n        Merge df1 and df2 on the lkey and rkey columns. The value columns have\n        the default suffixes, _x and _y, appended.\n\n        >>> merged = df1.merge(df2, left_on='lkey', right_on='rkey')\n        >>> merged.sort_values(by=['lkey', 'value_x', 'rkey', 'value_y'])  # doctest: +ELLIPSIS\n          lkey  value_x rkey  value_y\n        ...bar        2  bar        6\n        ...baz        3  baz        7\n        ...foo        1  foo        5\n        ...foo        1  foo        8\n        ...foo        5  foo        5\n        ...foo        5  foo        8\n\n        >>> left_psdf = ps.DataFrame({'A': [1, 2]})\n        >>> right_psdf = ps.DataFrame({'B': ['x', 'y']}, index=[1, 2])\n\n        >>> left_psdf.merge(right_psdf, left_index=True, right_index=True).sort_index()\n           A  B\n        1  2  x\n\n        >>> left_psdf.merge(right_psdf, left_index=True, right_index=True, how='left').sort_index()\n           A     B\n        0  1  None\n        1  2     x\n\n        >>> left_psdf.merge(right_psdf, left_index=True, right_index=True, how='right').sort_index()\n             A  B\n        1  2.0  x\n        2  NaN  y\n\n        >>> left_psdf.merge(right_psdf, left_index=True, right_index=True, how='outer').sort_index()\n             A     B\n        0  1.0  None\n        1  2.0     x\n        2  NaN     y\n\n        Notes\n        -----\n        As described in #263, joining string columns currently returns None for missing values\n            instead of NaN.\n        "

    def to_list(os: Optional[Union[(Name, List[Name])]]) -> List[Label]:
        if (os is None):
            return []
        elif is_name_like_tuple(os):
            return [cast(Label, os)]
        elif is_name_like_value(os):
            return [(os,)]
        else:
            return [(o if is_name_like_tuple(o) else (o,)) for o in os]
    if isinstance(right, ps.Series):
        right = right.to_frame()
    if on:
        if (left_on or right_on):
            raise ValueError('Can only pass argument "on" OR "left_on" and "right_on", not a combination of both.')
        left_key_names = list(map(self._internal.spark_column_name_for, to_list(on)))
        right_key_names = list(map(right._internal.spark_column_name_for, to_list(on)))
    else:
        if left_index:
            left_key_names = self._internal.index_spark_column_names
        else:
            left_key_names = list(map(self._internal.spark_column_name_for, to_list(left_on)))
        if right_index:
            right_key_names = right._internal.index_spark_column_names
        else:
            right_key_names = list(map(right._internal.spark_column_name_for, to_list(right_on)))
        if (left_key_names and (not right_key_names)):
            raise ValueError('Must pass right_on or right_index=True')
        if (right_key_names and (not left_key_names)):
            raise ValueError('Must pass left_on or left_index=True')
        if ((not left_key_names) and (not right_key_names)):
            common = list(self.columns.intersection(right.columns))
            if (len(common) == 0):
                raise ValueError('No common columns to perform merge on. Merge options: left_on=None, right_on=None, left_index=False, right_index=False')
            left_key_names = list(map(self._internal.spark_column_name_for, to_list(common)))
            right_key_names = list(map(right._internal.spark_column_name_for, to_list(common)))
        if (len(left_key_names) != len(right_key_names)):
            raise ValueError('len(left_keys) must equal len(right_keys)')
    right_prefix = '__right_'
    right_key_names = [(right_prefix + right_key_name) for right_key_name in right_key_names]
    how = validate_how(how)

    def resolve(internal: InternalFrame, side: str) -> InternalFrame:

        def rename(col: str) -> str:
            return '__{}_{}'.format(side, col)
        internal = internal.resolved_copy
        sdf = internal.spark_frame
        sdf = sdf.select(*[scol_for(sdf, col).alias(rename(col)) for col in sdf.columns if (col not in HIDDEN_COLUMNS)], *HIDDEN_COLUMNS)
        return internal.copy(spark_frame=sdf, index_spark_columns=[scol_for(sdf, rename(col)) for col in internal.index_spark_column_names], index_fields=[field.copy(name=rename(field.name)) for field in internal.index_fields], data_spark_columns=[scol_for(sdf, rename(col)) for col in internal.data_spark_column_names], data_fields=[field.copy(name=rename(field.name)) for field in internal.data_fields])
    left_internal = self._internal.resolved_copy
    right_internal = resolve(right._internal, 'right')
    left_table = left_internal.spark_frame.alias('left_table')
    right_table = right_internal.spark_frame.alias('right_table')
    left_key_columns = [scol_for(left_table, label) for label in left_key_names]
    right_key_columns = [scol_for(right_table, label) for label in right_key_names]
    join_condition = reduce((lambda x, y: (x & y)), [(lkey == rkey) for (lkey, rkey) in zip(left_key_columns, right_key_columns)])
    joined_table = left_table.join(right_table, join_condition, how=how)
    left_suffix = suffixes[0]
    right_suffix = suffixes[1]
    duplicate_columns = (set(left_internal.column_labels) & set(right_internal.column_labels))
    exprs = []
    data_columns = []
    column_labels = []

    def left_scol_for(label: Label) -> Column:
        return scol_for(left_table, left_internal.spark_column_name_for(label))

    def right_scol_for(label: Label) -> Column:
        return scol_for(right_table, right_internal.spark_column_name_for(label))
    for label in left_internal.column_labels:
        col = left_internal.spark_column_name_for(label)
        scol = left_scol_for(label)
        if (label in duplicate_columns):
            spark_column_name = left_internal.spark_column_name_for(label)
            if ((spark_column_name in left_key_names) and ((right_prefix + spark_column_name) in right_key_names)):
                right_scol = right_scol_for(label)
                if (how == 'right'):
                    scol = right_scol.alias(col)
                elif (how == 'full'):
                    scol = F.when(scol.isNotNull(), scol).otherwise(right_scol).alias(col)
                else:
                    pass
            else:
                col = (col + left_suffix)
                scol = scol.alias(col)
                label = tuple(([(str(label[0]) + left_suffix)] + list(label[1:])))
        exprs.append(scol)
        data_columns.append(col)
        column_labels.append(label)
    for label in right_internal.column_labels:
        col = right_internal.spark_column_name_for(label)[len(right_prefix):]
        scol = right_scol_for(label).alias(col)
        if (label in duplicate_columns):
            spark_column_name = left_internal.spark_column_name_for(label)
            if ((spark_column_name in left_key_names) and ((right_prefix + spark_column_name) in right_key_names)):
                continue
            else:
                col = (col + right_suffix)
                scol = scol.alias(col)
                label = tuple(([(str(label[0]) + right_suffix)] + list(label[1:])))
        exprs.append(scol)
        data_columns.append(col)
        column_labels.append(label)
    left_index_scols = left_internal.index_spark_columns
    right_index_scols = right_internal.index_spark_columns
    if left_index:
        if right_index:
            if (how in ('inner', 'left')):
                exprs.extend(left_index_scols)
                index_spark_column_names = left_internal.index_spark_column_names
                index_names = left_internal.index_names
            elif (how == 'right'):
                exprs.extend(right_index_scols)
                index_spark_column_names = right_internal.index_spark_column_names
                index_names = right_internal.index_names
            else:
                index_spark_column_names = left_internal.index_spark_column_names
                index_names = left_internal.index_names
                for (col, left_scol, right_scol) in zip(index_spark_column_names, left_index_scols, right_index_scols):
                    scol = F.when(left_scol.isNotNull(), left_scol).otherwise(right_scol)
                    exprs.append(scol.alias(col))
        else:
            exprs.extend(right_index_scols)
            index_spark_column_names = right_internal.index_spark_column_names
            index_names = right_internal.index_names
    elif right_index:
        exprs.extend(left_index_scols)
        index_spark_column_names = left_internal.index_spark_column_names
        index_names = left_internal.index_names
    else:
        index_spark_column_names = []
        index_names = []
    selected_columns = joined_table.select(*exprs)
    internal = InternalFrame(spark_frame=selected_columns, index_spark_columns=[scol_for(selected_columns, col) for col in index_spark_column_names], index_names=index_names, column_labels=column_labels, data_spark_columns=[scol_for(selected_columns, col) for col in data_columns])
    return DataFrame(internal)