def join(self, other, on=None, left_on=None, right_on=None, lsuffix='', rsuffix='', how='left', inplace=False):
        """Return a DataFrame joined with other DataFrames, matched by columns/expression on/left_on/right_on

        If neither on/left_on/right_on is given, the join is done by simply adding the columns (i.e. on the implicit
        row index).

        Note: The filters will be ignored when joining, the full DataFrame will be joined (since filters may
        change). If either DataFrame is heavily filtered (contains just a small number of rows) consider running
        :func:`DataFrame.extract` first.

        Example:

        >>> a = np.array(['a', 'b', 'c'])
        >>> x = np.arange(1,4)
        >>> ds1 = vaex.from_arrays(a=a, x=x)
        >>> b = np.array(['a', 'b', 'd'])
        >>> y = x**2
        >>> ds2 = vaex.from_arrays(b=b, y=y)
        >>> ds1.join(ds2, left_on='a', right_on='b')

        :param other: Other DataFrame to join with (the right side)
        :param on: default key for the left table (self)
        :param left_on: key for the left table (self), overrides on
        :param right_on: default key for the right table (other), overrides on
        :param lsuffix: suffix to add to the left column names in case of a name collision
        :param rsuffix: similar for the right
        :param how: how to join, 'left' keeps all rows on the left, and adds columns (with possible missing values)
                'right' is similar with self and other swapped.
        :param inplace: {inplace}
        :return:
        """
        ds = self if inplace else self.copy()
        if how == 'left':
            left = ds
            right = other
        elif how == 'right':
            left = other
            right = ds
            lsuffix, rsuffix = rsuffix, lsuffix
            left_on, right_on = right_on, left_on
        else:
            raise ValueError('join type not supported: {}, only left and right'.format(how))

        for name in right:
            if name in left and name + rsuffix == name + lsuffix:
                raise ValueError('column name collision: {} exists in both column, and no proper suffix given'
                                 .format(name))

        right = right.extract()  # get rid of filters and active_range
        assert left.length_unfiltered() == left.length_original()
        N = left.length_unfiltered()
        N_other = len(right)
        left_on = left_on or on
        right_on = right_on or on
        if left_on is None and right_on is None:
            for name in right:
                right_name = name
                if name in left:
                    left.rename_column(name, name + lsuffix)
                    right_name = name + rsuffix
                if name in right.virtual_columns:
                    left.add_virtual_column(right_name, right.virtual_columns[name])
                else:
                    left.add_column(right_name, right.columns[name])
        else:
            left_values = left.evaluate(left_on, filtered=False)
            right_values = right.evaluate(right_on)
            # maps from the left_values to row #
            if np.ma.isMaskedArray(left_values):
                mask = ~left_values.mask
                left_values = left_values.data
                index_left = dict(zip(left_values[mask], np.arange(N)[mask]))
            else:
                index_left = dict(zip(left_values, np.arange(N)))
            # idem for right
            if np.ma.isMaskedArray(right_values):
                mask = ~right_values.mask
                right_values = right_values.data
                index_other = dict(zip(right_values[mask], np.arange(N_other)[mask]))
            else:
                index_other = dict(zip(right_values, np.arange(N_other)))

            # we do a left join, find all rows of the right DataFrame
            # that has an entry on the left
            # for each row in the right
            # find which row it needs to go to in the right
            # from_indices = np.zeros(N_other, dtype=np.int64)  # row # of right
            # to_indices = np.zeros(N_other, dtype=np.int64)    # goes to row # on the left
            # keep a boolean mask of which rows are found
            left_mask = np.ones(N, dtype=np.bool)
            # and which row they point to in the right
            left_row_to_right = np.zeros(N, dtype=np.int64) - 1
            for i in range(N_other):
                left_row = index_left.get(right_values[i])
                if left_row is not None:
                    left_mask[left_row] = False  # unmask, it exists
                    left_row_to_right[left_row] = i

            lookup = np.ma.array(left_row_to_right, mask=left_mask)
            for name in right:
                right_name = name
                if name in left:
                    left.rename_column(name, name + lsuffix)
                    right_name = name + rsuffix
                if name in right.virtual_columns:
                    left.add_virtual_column(right_name, right.virtual_columns[name])
                else:
                    left.add_column(right_name, ColumnIndexed(right, lookup, name))
        return left
Return a DataFrame joined with other DataFrames, matched by columns/expression on/left_on/right_on

        If neither on/left_on/right_on is given, the join is done by simply adding the columns (i.e. on the implicit
        row index).

        Note: The filters will be ignored when joining, the full DataFrame will be joined (since filters may
        change). If either DataFrame is heavily filtered (contains just a small number of rows) consider running
        :func:`DataFrame.extract` first.

        Example:

        >>> a = np.array(['a', 'b', 'c'])
        >>> x = np.arange(1,4)
        >>> ds1 = vaex.from_arrays(a=a, x=x)
        >>> b = np.array(['a', 'b', 'd'])
        >>> y = x**2
        >>> ds2 = vaex.from_arrays(b=b, y=y)
        >>> ds1.join(ds2, left_on='a', right_on='b')

        :param other: Other DataFrame to join with (the right side)
        :param on: default key for the left table (self)
        :param left_on: key for the left table (self), overrides on
        :param right_on: default key for the right table (other), overrides on
        :param lsuffix: suffix to add to the left column names in case of a name collision
        :param rsuffix: similar for the right
        :param how: how to join, 'left' keeps all rows on the left, and adds columns (with possible missing values)
                'right' is similar with self and other swapped.
        :param inplace: {inplace}
        :return: