1"""
2Concat routines.
3"""
4
5from __future__ import annotations
6
7from collections import abc
8from itertools import pairwise
9import types
10from typing import (
11 TYPE_CHECKING,
12 Literal,
13 cast,
14 overload,
15)
16import warnings
17
18import numpy as np
19
20from pandas._libs import lib
21from pandas.errors import Pandas4Warning
22from pandas.util._decorators import set_module
23from pandas.util._exceptions import find_stack_level
24
25from pandas.core.dtypes.common import (
26 is_bool,
27 is_scalar,
28)
29from pandas.core.dtypes.concat import concat_compat
30from pandas.core.dtypes.generic import (
31 ABCDataFrame,
32 ABCSeries,
33)
34from pandas.core.dtypes.missing import isna
35
36from pandas.core.arrays.categorical import (
37 factorize_from_iterable,
38 factorize_from_iterables,
39)
40import pandas.core.common as com
41from pandas.core.indexes.api import (
42 Index,
43 MultiIndex,
44 all_indexes_same,
45 default_index,
46 ensure_index,
47 get_objs_combined_axis,
48 get_unanimous_names,
49 union_indexes,
50)
51from pandas.core.indexes.datetimes import DatetimeIndex
52from pandas.core.internals import concatenate_managers
53
54if TYPE_CHECKING:
55 from collections.abc import (
56 Callable,
57 Hashable,
58 Iterable,
59 Mapping,
60 )
61
62 from pandas._typing import (
63 Axis,
64 AxisInt,
65 HashableT,
66 )
67
68 from pandas import (
69 DataFrame,
70 Series,
71 )
72
73# ---------------------------------------------------------------------
74# Concatenate DataFrame objects
75
76
77@overload
78def concat(
79 objs: Iterable[DataFrame] | Mapping[HashableT, DataFrame],
80 *,
81 axis: Literal[0, "index"] = ...,
82 join: str = ...,
83 ignore_index: bool = ...,
84 keys: Iterable[Hashable] | None = ...,
85 levels=...,
86 names: list[HashableT] | None = ...,
87 verify_integrity: bool = ...,
88 sort: bool = ...,
89 copy: bool | lib.NoDefault = ...,
90 union_categories: bool = ...,
91) -> DataFrame: ...
92
93
94@overload
95def concat(
96 objs: Iterable[Series] | Mapping[HashableT, Series],
97 *,
98 axis: Literal[0, "index"] = ...,
99 join: str = ...,
100 ignore_index: bool = ...,
101 keys: Iterable[Hashable] | None = ...,
102 levels=...,
103 names: list[HashableT] | None = ...,
104 verify_integrity: bool = ...,
105 sort: bool = ...,
106 copy: bool | lib.NoDefault = ...,
107 union_categories: bool = ...,
108) -> Series: ...
109
110
111@overload
112def concat(
113 objs: Iterable[Series | DataFrame] | Mapping[HashableT, Series | DataFrame],
114 *,
115 axis: Literal[0, "index"] = ...,
116 join: str = ...,
117 ignore_index: bool = ...,
118 keys: Iterable[Hashable] | None = ...,
119 levels=...,
120 names: list[HashableT] | None = ...,
121 verify_integrity: bool = ...,
122 sort: bool = ...,
123 copy: bool | lib.NoDefault = ...,
124 union_categories: bool = ...,
125) -> DataFrame | Series: ...
126
127
128@overload
129def concat(
130 objs: Iterable[Series | DataFrame] | Mapping[HashableT, Series | DataFrame],
131 *,
132 axis: Literal[1, "columns"],
133 join: str = ...,
134 ignore_index: bool = ...,
135 keys: Iterable[Hashable] | None = ...,
136 levels=...,
137 names: list[HashableT] | None = ...,
138 verify_integrity: bool = ...,
139 sort: bool = ...,
140 copy: bool | lib.NoDefault = ...,
141 union_categories: bool = ...,
142) -> DataFrame: ...
143
144
145@overload
146def concat(
147 objs: Iterable[Series | DataFrame] | Mapping[HashableT, Series | DataFrame],
148 *,
149 axis: Axis = ...,
150 join: str = ...,
151 ignore_index: bool = ...,
152 keys: Iterable[Hashable] | None = ...,
153 levels=...,
154 names: list[HashableT] | None = ...,
155 verify_integrity: bool = ...,
156 sort: bool = ...,
157 copy: bool | lib.NoDefault = ...,
158 union_categories: bool = ...,
159) -> DataFrame | Series: ...
160
161
162@set_module("pandas")
163def concat(
164 objs: Iterable[Series | DataFrame] | Mapping[HashableT, Series | DataFrame],
165 *,
166 axis: Axis = 0,
167 join: str = "outer",
168 ignore_index: bool = False,
169 keys: Iterable[Hashable] | None = None,
170 levels=None,
171 names: list[HashableT] | None = None,
172 verify_integrity: bool = False,
173 sort: bool | lib.NoDefault = lib.no_default,
174 copy: bool | lib.NoDefault = lib.no_default,
175 union_categories: bool = False,
176) -> DataFrame | Series:
177 """
178 Concatenate pandas objects along a particular axis.
179
180 Allows optional set logic along the other axes.
181
182 Can also add a layer of hierarchical indexing on the concatenation axis,
183 which may be useful if the labels are the same (or overlapping) on
184 the passed axis number.
185
186 Parameters
187 ----------
188 objs : an iterable or mapping of Series or DataFrame objects
189 If a mapping is passed, the keys will be used as the `keys`
190 argument, unless it is passed, in which case the values will be
191 selected (see below). Any None objects will be dropped silently unless
192 they are all None in which case a ValueError will be raised.
193 axis : {0/'index', 1/'columns'}, default 0
194 The axis to concatenate along.
195 join : {'inner', 'outer'}, default 'outer'
196 How to handle indexes on other axis (or axes).
197 ignore_index : bool, default False
198 If True, do not use the index values along the concatenation axis. The
199 resulting axis will be labeled 0, ..., n - 1. This is useful if you are
200 concatenating objects where the concatenation axis does not have
201 meaningful indexing information. Note the index values on the other
202 axes are still respected in the join.
203 keys : sequence, default None
204 If multiple levels passed, should contain tuples. Construct
205 hierarchical index using the passed keys as the outermost level.
206 levels : list of sequences, default None
207 Specific levels (unique values) to use for constructing a
208 MultiIndex. Otherwise they will be inferred from the keys.
209 names : list, default None
210 Names for the levels in the resulting hierarchical index.
211 verify_integrity : bool, default False
212 Check whether the new concatenated axis contains duplicates. This can
213 be very expensive relative to the actual data concatenation.
214 sort : bool, default False
215 Sort non-concatenation axis. One exception to this is when the
216 non-concatenation axis is a DatetimeIndex and join='outer' and the axis is
217 not already aligned. In that case, the non-concatenation axis is always
218 sorted lexicographically.
219 copy : bool, default False
220 This keyword is now ignored; changing its value will have no
221 impact on the method.
222
223 .. deprecated:: 3.0.0
224
225 This keyword is ignored and will be removed in pandas 4.0. Since
226 pandas 3.0, this method always returns a new object using a lazy
227 copy mechanism that defers copies until necessary
228 (Copy-on-Write). See the `user guide on Copy-on-Write
229 <https://pandas.pydata.org/docs/dev/user_guide/migration.html>`__
230 for more details.
231
232 union_categories : bool, default False
233 If True and all values for a given column have categorical dtype,
234 the resulting column will preserve categorical dtype with the
235 union of the categories, rather than casting to the underlying
236 dtype. Categories whose dtypes differ are cast to a common dtype.
237 The result is unordered unless every input is ordered with the
238 same categories (after any casting to a common dtype) in the same
239 order, in which case the result is ordered.
240 Has no effect when concatenating mixed categorical and
241 non-categorical data.
242
243 .. versionadded:: 3.1.0
244
245 Returns
246 -------
247 object, type of objs
248 When concatenating all ``Series`` along the index (axis=0), a
249 ``Series`` is returned. When ``objs`` contains at least one
250 ``DataFrame``, a ``DataFrame`` is returned. When concatenating along
251 the columns (axis=1), a ``DataFrame`` is returned.
252
253 See Also
254 --------
255 DataFrame.join : Join DataFrames using indexes.
256 DataFrame.merge : Merge DataFrames by indexes or columns.
257
258 Notes
259 -----
260 The keys, levels, and names arguments are all optional.
261
262 A walkthrough of how this method fits in with other tools for combining
263 pandas objects can be found `here
264 <https://pandas.pydata.org/pandas-docs/stable/user_guide/merging.html>`__.
265
266 It is not recommended to build DataFrames by adding single rows in a
267 for loop. Build a list of rows and make a DataFrame in a single concat.
268
269 Examples
270 --------
271 Combine two ``Series``.
272
273 >>> s1 = pd.Series(["a", "b"])
274 >>> s2 = pd.Series(["c", "d"])
275 >>> pd.concat([s1, s2])
276 0 a
277 1 b
278 0 c
279 1 d
280 dtype: str
281
282 Clear the existing index and reset it in the result
283 by setting the ``ignore_index`` option to ``True``.
284
285 >>> pd.concat([s1, s2], ignore_index=True)
286 0 a
287 1 b
288 2 c
289 3 d
290 dtype: str
291
292 Add a hierarchical index at the outermost level of
293 the data with the ``keys`` option.
294
295 >>> pd.concat([s1, s2], keys=["s1", "s2"])
296 s1 0 a
297 1 b
298 s2 0 c
299 1 d
300 dtype: str
301
302 Label the index keys you create with the ``names`` option.
303
304 >>> pd.concat([s1, s2], keys=["s1", "s2"], names=["Series name", "Row ID"])
305 Series name Row ID
306 s1 0 a
307 1 b
308 s2 0 c
309 1 d
310 dtype: str
311
312 Combine two ``DataFrame`` objects with identical columns.
313
314 >>> df1 = pd.DataFrame([["a", 1], ["b", 2]], columns=["letter", "number"])
315 >>> df1
316 letter number
317 0 a 1
318 1 b 2
319 >>> df2 = pd.DataFrame([["c", 3], ["d", 4]], columns=["letter", "number"])
320 >>> df2
321 letter number
322 0 c 3
323 1 d 4
324 >>> pd.concat([df1, df2])
325 letter number
326 0 a 1
327 1 b 2
328 0 c 3
329 1 d 4
330
331 Combine ``DataFrame`` objects with overlapping columns
332 and return everything. Columns outside the intersection will
333 be filled with ``NaN`` values.
334
335 >>> df3 = pd.DataFrame(
336 ... [["c", 3, "cat"], ["d", 4, "dog"]], columns=["letter", "number", "animal"]
337 ... )
338 >>> df3
339 letter number animal
340 0 c 3 cat
341 1 d 4 dog
342 >>> pd.concat([df1, df3], sort=False)
343 letter number animal
344 0 a 1 NaN
345 1 b 2 NaN
346 0 c 3 cat
347 1 d 4 dog
348
349 Combine ``DataFrame`` objects with overlapping columns
350 and return only those that are shared by passing ``inner`` to
351 the ``join`` keyword argument.
352
353 >>> pd.concat([df1, df3], join="inner")
354 letter number
355 0 a 1
356 1 b 2
357 0 c 3
358 1 d 4
359
360 Combine ``DataFrame`` objects horizontally along the x axis by
361 passing in ``axis=1``.
362
363 >>> df4 = pd.DataFrame(
364 ... [["bird", "polly"], ["monkey", "george"]], columns=["animal", "name"]
365 ... )
366 >>> pd.concat([df1, df4], axis=1)
367 letter number animal name
368 0 a 1 bird polly
369 1 b 2 monkey george
370
371 Prevent the result from including duplicate index values with the
372 ``verify_integrity`` option.
373
374 >>> df5 = pd.DataFrame([1], index=["a"])
375 >>> df5
376 0
377 a 1
378 >>> df6 = pd.DataFrame([2], index=["a"])
379 >>> df6
380 0
381 a 2
382 >>> pd.concat([df5, df6], verify_integrity=True)
383 Traceback (most recent call last):
384 ...
385 ValueError: Indexes have overlapping values: ['a']
386
387 Append a single row to the end of a ``DataFrame`` object.
388
389 >>> df7 = pd.DataFrame({"a": 1, "b": 2}, index=[0])
390 >>> df7
391 a b
392 0 1 2
393 >>> new_row = pd.Series({"a": 3, "b": 4})
394 >>> new_row
395 a 3
396 b 4
397 dtype: int64
398 >>> pd.concat([df7, new_row.to_frame().T], ignore_index=True)
399 a b
400 0 1 2
401 1 3 4
402 """
403 if ignore_index and keys is not None:
404 raise ValueError(
405 f"Cannot set {ignore_index=} and specify keys. Either should be used."
406 )
407
408 if copy is not lib.no_default:
409 warnings.warn(
410 "The copy keyword is deprecated and will be removed in a future "
411 "version. Copy-on-Write is active in pandas since 3.0 which utilizes "
412 "a lazy copy mechanism that defers copies until necessary. Use "
413 ".copy() to make an eager copy if necessary.",
414 Pandas4Warning,
415 stacklevel=find_stack_level(),
416 )
417 if join == "outer":
418 intersect = False
419 elif join == "inner":
420 intersect = True
421 else: # pragma: no cover
422 raise ValueError(
423 "Only can inner (intersect) or outer (union) join the other axis"
424 )
425
426 objs, keys, ndims = _clean_keys_and_objs(objs, keys)
427
428 if sort is lib.no_default:
429 if axis == 0:
430 non_concat_axis = [
431 obj.columns if isinstance(obj, ABCDataFrame) else Index([obj.name])
432 for obj in objs
433 ]
434 else:
435 non_concat_axis = [obj.index for obj in objs]
436
437 if (
438 intersect
439 or any(not isinstance(index, DatetimeIndex) for index in non_concat_axis)
440 or all(prev is curr for prev, curr in pairwise(non_concat_axis))
441 or (
442 all(
443 prev[-1] <= curr[0] and prev.is_monotonic_increasing
444 for prev, curr in pairwise(non_concat_axis)
445 if not prev.empty and not curr.empty
446 )
447 and non_concat_axis[-1].is_monotonic_increasing
448 )
449 ):
450 # Sorting or not will not impact the result.
451 sort = False
452 elif not is_bool(sort):
453 raise ValueError(
454 f"The 'sort' keyword only accepts boolean values; {sort} was passed."
455 )
456 else:
457 sort = bool(sort)
458
459 # select an object to be our result reference
460 sample, objs = _get_sample_object(objs, ndims, keys, names, levels, intersect)
461
462 # Standardize axis parameter to int
463 if sample.ndim == 1:
464 from pandas import DataFrame
465
466 bm_axis = DataFrame._get_axis_number(axis)
467 is_frame = False
468 is_series = True
469 else:
470 bm_axis = sample._get_axis_number(axis)
471 is_frame = True
472 is_series = False
473
474 # Need to flip BlockManager axis in the DataFrame special case
475 bm_axis = sample._get_block_manager_axis(bm_axis)
476
477 # if we have mixed ndims, then convert to highest ndim
478 # creating column numbers as needed
479 if len(ndims) > 1:
480 objs = _sanitize_mixed_ndim(objs, sample, ignore_index, bm_axis)
481
482 orig_axis = axis
483 axis = 1 - bm_axis if is_frame else 0
484 names = names or getattr(keys, "names", None)
485 result = _get_result(
486 objs,
487 is_series,
488 bm_axis,
489 ignore_index,
490 intersect,
491 sort,
492 keys,
493 levels,
494 verify_integrity,
495 names,
496 axis,
497 union_categories,
498 )
499
500 # When result is a Series, there is no other axis to sort.
501 if sort is lib.no_default and not isinstance(result, ABCSeries):
502 if orig_axis == 0:
503 non_concat_axis = [
504 obj.columns if isinstance(obj, ABCDataFrame) else Index([obj.name])
505 for obj in objs
506 ]
507 else:
508 non_concat_axis = [obj.index for obj in objs]
509 no_sort_result_index, _ = union_indexes(non_concat_axis, sort=False)
510 orig = result.index if orig_axis == 1 else result.columns
511 if not no_sort_result_index.equals(orig):
512 msg = (
513 "Sorting by default when concatenating all DatetimeIndex is "
514 "deprecated. In the future, pandas will respect the default "
515 "of `sort=False`. Specify `sort=True` or `sort=False` to "
516 "silence this message. If you see this warnings when not "
517 "directly calling concat, report a bug to pandas."
518 )
519 warnings.warn(msg, Pandas4Warning, stacklevel=find_stack_level())
520
521 return result
522
523
524def _sanitize_mixed_ndim(
525 objs: list[Series | DataFrame],
526 sample: Series | DataFrame,
527 ignore_index: bool,
528 axis: AxisInt,
529) -> list[Series | DataFrame]:
530 # if we have mixed ndims, then convert to highest ndim
531 # creating column numbers as needed
532
533 new_objs = []
534
535 current_column = 0
536 max_ndim = sample.ndim
537 for obj in objs:
538 ndim = obj.ndim
539 if ndim == max_ndim:
540 pass
541
542 elif ndim != max_ndim - 1:
543 raise ValueError(
544 "cannot concatenate unaligned mixed dimensional NDFrame objects"
545 )
546
547 else:
548 name = getattr(obj, "name", None)
549 rename_columns = False
550 if ignore_index or name is None:
551 if axis == 1:
552 # doing a row-wise concatenation so need everything
553 # to line up
554 if name is None:
555 name = 0
556 rename_columns = True
557 # doing a column-wise concatenation so need series
558 # to have unique names
559 elif name is None:
560 rename_columns = True
561 name = current_column
562 current_column += 1
563 obj = sample._constructor(obj, copy=False)
564 if isinstance(obj, ABCDataFrame) and rename_columns:
565 obj.columns = range(name, name + 1, 1)
566 else:
567 obj = sample._constructor({name: obj}, copy=False)
568
569 new_objs.append(obj)
570
571 return new_objs
572
573
574def _get_result(
575 objs: list[Series | DataFrame],
576 is_series: bool,
577 bm_axis: AxisInt,
578 ignore_index: bool,
579 intersect: bool,
580 sort: bool | lib.NoDefault,
581 keys: Iterable[Hashable] | None,
582 levels,
583 verify_integrity: bool,
584 names: list[HashableT] | None,
585 axis: AxisInt,
586 union_categories: bool = False,
587):
588 cons: Callable[..., DataFrame | Series]
589 sample: DataFrame | Series
590
591 # series only
592 if is_series:
593 sample = cast("Series", objs[0])
594
595 # stack blocks
596 if bm_axis == 0:
597 name = com.consensus_name_attr(objs)
598 cons = sample._constructor
599
600 arrs = [ser._values for ser in objs]
601
602 res = concat_compat(arrs, axis=0, union_categories=union_categories)
603
604 if ignore_index:
605 new_index: Index = default_index(len(res))
606 else:
607 new_index = _get_concat_axis_series(
608 objs,
609 ignore_index,
610 bm_axis,
611 keys,
612 levels,
613 verify_integrity,
614 names,
615 )
616
617 mgr = type(sample._mgr).from_array(res, index=new_index)
618
619 result = sample._constructor_from_mgr(mgr, axes=mgr.axes)
620 result._name = name
621 return result.__finalize__(
622 types.SimpleNamespace(input_objs=objs, objs=objs), method="concat"
623 )
624
625 # combine as columns in a frame
626 else:
627 data = dict(enumerate(objs))
628
629 # GH28330 Preserves subclassed objects through concat
630 cons = sample._constructor_expanddim
631
632 index, _ = get_objs_combined_axis(
633 objs,
634 axis=objs[0]._get_block_manager_axis(0),
635 intersect=intersect,
636 sort=sort,
637 )
638 columns = _get_concat_axis_series(
639 objs, ignore_index, bm_axis, keys, levels, verify_integrity, names
640 )
641 df = cons(data, index=index, copy=False)
642 df.columns = columns
643 return df.__finalize__(
644 types.SimpleNamespace(input_objs=objs, objs=objs), method="concat"
645 )
646
647 # combine block managers
648 else:
649 sample = cast("DataFrame", objs[0])
650
651 mgrs_indexers = []
652 result_axes, all_equal = new_axes(
653 objs,
654 bm_axis,
655 intersect,
656 sort,
657 keys,
658 names,
659 axis,
660 levels,
661 verify_integrity,
662 ignore_index,
663 )
664 for obj in objs:
665 indexers = {}
666 for ax, new_labels in enumerate(result_axes):
667 # ::-1 to convert BlockManager ax to DataFrame ax
668 if ax == bm_axis:
669 # Suppress reindexing on concat axis
670 continue
671
672 # 1-ax to convert BlockManager axis to DataFrame axis
673 obj_labels = obj.axes[1 - ax]
674 if not all_equal[ax] and not new_labels.equals(obj_labels):
675 indexers[ax] = obj_labels.get_indexer(new_labels)
676
677 mgrs_indexers.append((obj._mgr, indexers))
678
679 new_data = concatenate_managers(
680 mgrs_indexers,
681 result_axes,
682 concat_axis=bm_axis,
683 copy=False,
684 union_categories=union_categories,
685 )
686
687 out = sample._constructor_from_mgr(new_data, axes=new_data.axes)
688 return out.__finalize__(
689 types.SimpleNamespace(input_objs=objs, objs=objs), method="concat"
690 )
691
692
693def new_axes(
694 objs: list[Series | DataFrame],
695 bm_axis: AxisInt,
696 intersect: bool,
697 sort: bool | lib.NoDefault,
698 keys: Iterable[Hashable] | None,
699 names: list[HashableT] | None,
700 axis: AxisInt,
701 levels,
702 verify_integrity: bool,
703 ignore_index: bool,
704) -> tuple[list[Index], list[bool]]:
705 """Return the new [index, column] result for concat."""
706 result_axes: list[Index] = []
707 all_equal: list[bool] = []
708
709 for i in range(2):
710 if i == bm_axis:
711 result_axis = _get_concat_axis_dataframe(
712 objs,
713 axis,
714 ignore_index,
715 keys,
716 names,
717 levels,
718 verify_integrity,
719 )
720 is_all_equal = False
721 else:
722 result_axis, is_all_equal = get_objs_combined_axis(
723 objs,
724 axis=objs[0]._get_block_manager_axis(i),
725 intersect=intersect,
726 sort=sort,
727 )
728
729 result_axes.append(result_axis)
730 all_equal.append(is_all_equal)
731
732 return result_axes, all_equal
733
734
735def _get_concat_axis_series(
736 objs: list[Series | DataFrame],
737 ignore_index: bool,
738 bm_axis: AxisInt,
739 keys: Iterable[Hashable] | None,
740 levels,
741 verify_integrity: bool,
742 names: list[HashableT] | None,
743) -> Index:
744 """Return result concat axis when concatenating Series objects."""
745 if ignore_index:
746 return default_index(len(objs))
747 elif bm_axis == 0:
748 indexes = [x.index for x in objs]
749 if keys is None:
750 if levels is not None:
751 raise ValueError("levels supported only when keys is not None")
752 concat_axis = _concat_indexes(indexes)
753 else:
754 concat_axis = _make_concat_multiindex(indexes, keys, levels, names)
755 if verify_integrity and not concat_axis.is_unique:
756 overlap = concat_axis[concat_axis.duplicated()].unique()
757 raise ValueError(f"Indexes have overlapping values: {overlap}")
758 return concat_axis
759 elif keys is None:
760 result_names: list[Hashable] = [None] * len(objs)
761 num = 0
762 has_names = False
763 for i, x in enumerate(objs):
764 if x.ndim != 1:
765 raise TypeError(
766 f"Cannot concatenate type 'Series' with "
767 f"object of type '{type(x).__name__}'"
768 )
769 if x.name is not None:
770 result_names[i] = x.name
771 has_names = True
772 else:
773 result_names[i] = num
774 num += 1
775 if has_names:
776 return Index(result_names)
777 else:
778 return default_index(len(objs))
779 else:
780 return ensure_index(keys).set_names(names) # type: ignore[arg-type]
781
782
783def _get_concat_axis_dataframe(
784 objs: list[Series | DataFrame],
785 axis: AxisInt,
786 ignore_index: bool,
787 keys: Iterable[Hashable] | None,
788 names: list[HashableT] | None,
789 levels,
790 verify_integrity: bool,
791) -> Index:
792 """Return result concat axis when concatenating DataFrame objects."""
793 indexes_gen = (x.axes[axis] for x in objs)
794
795 if ignore_index:
796 return default_index(sum(len(i) for i in indexes_gen))
797 else:
798 indexes = list(indexes_gen)
799
800 if keys is None:
801 if levels is not None:
802 raise ValueError("levels supported only when keys is not None")
803 concat_axis = _concat_indexes(indexes)
804 else:
805 concat_axis = _make_concat_multiindex(indexes, keys, levels, names)
806
807 if verify_integrity and not concat_axis.is_unique:
808 overlap = concat_axis[concat_axis.duplicated()].unique()
809 raise ValueError(f"Indexes have overlapping values: {overlap}")
810
811 return concat_axis
812
813
814def _clean_keys_and_objs(
815 objs: Iterable[Series | DataFrame] | Mapping[HashableT, Series | DataFrame],
816 keys,
817) -> tuple[list[Series | DataFrame], Index | None, set[int]]:
818 """
819 Returns
820 -------
821 clean_objs : list[Series | DataFrame]
822 List of DataFrame and Series with Nones removed.
823 keys : Index | None
824 None if keys was None
825 Index if objs was a Mapping or keys was not None. Filtered where objs was None.
826 ndim : set[int]
827 Unique .ndim attribute of obj encountered.
828 """
829 if isinstance(objs, abc.Mapping):
830 if keys is None:
831 keys = objs.keys()
832 objs = [objs[k] for k in keys]
833 elif isinstance(objs, (ABCSeries, ABCDataFrame)) or is_scalar(objs):
834 raise TypeError(
835 "first argument must be an iterable of pandas "
836 f'objects, you passed an object of type "{type(objs).__name__}"'
837 )
838 elif not isinstance(objs, abc.Sized):
839 objs = list(objs)
840
841 if len(objs) == 0:
842 raise ValueError("No objects to concatenate")
843
844 if keys is not None:
845 if not isinstance(keys, Index):
846 keys = Index(keys)
847 if len(keys) != len(objs):
848 # GH#43485
849 raise ValueError(
850 f"The length of the keys ({len(keys)}) must match "
851 f"the length of the objects to concatenate ({len(objs)})"
852 )
853
854 # GH#1649
855 key_indices = []
856 clean_objs = []
857 ndims = set()
858 for i, obj in enumerate(objs):
859 if obj is None:
860 continue
861 elif isinstance(obj, (ABCSeries, ABCDataFrame)):
862 key_indices.append(i)
863 clean_objs.append(obj)
864 ndims.add(obj.ndim)
865 else:
866 msg = (
867 f"cannot concatenate object of type '{type(obj)}'; "
868 "only Series and DataFrame objs are valid"
869 )
870 raise TypeError(msg)
871
872 if keys is not None and len(key_indices) < len(keys):
873 keys = keys.take(key_indices)
874
875 if len(clean_objs) == 0:
876 raise ValueError("All objects passed were None")
877
878 return clean_objs, keys, ndims
879
880
881def _get_sample_object(
882 objs: list[Series | DataFrame],
883 ndims: set[int],
884 keys,
885 names,
886 levels,
887 intersect: bool,
888) -> tuple[Series | DataFrame, list[Series | DataFrame]]:
889 # get the sample
890 # want the highest ndim that we have, and must be non-empty
891 # unless all objs are empty
892 if len(ndims) > 1:
893 max_ndim = max(ndims)
894 for obj in objs:
895 if obj.ndim == max_ndim and sum(obj.shape): # type: ignore[arg-type]
896 return obj, objs
897 elif keys is None and names is None and levels is None and not intersect:
898 # filter out the empties if we have not multi-index possibilities
899 # note to keep empty Series as it affects the result columns / name
900 if ndims.pop() == 2:
901 non_empties = [obj for obj in objs if sum(obj.shape)]
902 else:
903 non_empties = objs
904
905 if len(non_empties):
906 return non_empties[0], non_empties
907
908 return objs[0], objs
909
910
911def _concat_indexes(indexes) -> Index:
912 return indexes[0].append(indexes[1:])
913
914
915def validate_unique_levels(levels: list[Index]) -> None:
916 for level in levels:
917 if not level.is_unique:
918 raise ValueError(f"Level values not unique: {level.tolist()}")
919
920
921def _make_concat_multiindex(indexes, keys, levels=None, names=None) -> MultiIndex:
922 if (levels is None and isinstance(keys[0], tuple)) or (
923 levels is not None and len(levels) > 1
924 ):
925 zipped = list(zip(*keys, strict=True))
926 if names is None:
927 names = [None] * len(zipped)
928
929 if levels is None:
930 _, levels = factorize_from_iterables(zipped)
931 else:
932 levels = [ensure_index(x) for x in levels]
933 validate_unique_levels(levels)
934 else:
935 zipped = [keys]
936 if names is None:
937 names = [None]
938
939 if levels is None:
940 levels = [ensure_index(keys).unique()]
941 else:
942 levels = [ensure_index(x) for x in levels]
943 validate_unique_levels(levels)
944
945 if not all_indexes_same(indexes):
946 codes_list = []
947
948 # things are potentially different sizes, so compute the exact codes
949 # for each level and pass those to MultiIndex.from_arrays
950
951 for hlevel, level in zip(zipped, levels, strict=True):
952 to_concat = []
953 if isinstance(hlevel, Index) and hlevel.equals(level):
954 lens = [len(idx) for idx in indexes]
955 codes_list.append(np.repeat(np.arange(len(hlevel)), lens))
956 else:
957 for key, index in zip(hlevel, indexes, strict=True):
958 # Find matching codes, include matching nan values as equal.
959 mask = (isna(level) & isna(key)) | (level == key)
960 if not mask.any():
961 raise ValueError(f"Key {key} not in level {level}")
962 i = np.nonzero(mask)[0][0]
963
964 to_concat.append(np.repeat(i, len(index)))
965 codes_list.append(np.concatenate(to_concat))
966
967 concat_index = _concat_indexes(indexes)
968
969 # these go at the end
970 if isinstance(concat_index, MultiIndex):
971 levels.extend(concat_index.levels)
972 codes_list.extend(concat_index.codes)
973 else:
974 codes, categories = factorize_from_iterable(concat_index)
975 levels.append(categories)
976 codes_list.append(codes)
977
978 if len(names) == len(levels):
979 names = list(names)
980 else:
981 # make sure that all of the passed indices have the same nlevels
982 nlevels_set = {idx.nlevels for idx in indexes}
983 if len(nlevels_set) != 1:
984 # GH#25413
985 raise ValueError(
986 "Cannot concat indices that do not have the same number "
987 f"of levels: got levels {sorted(nlevels_set)}. When passing "
988 "keys=, all objects must have the same index nlevels."
989 )
990
991 # also copies
992 names = list(names) + list(get_unanimous_names(*indexes))
993
994 return MultiIndex(
995 levels=levels, codes=codes_list, names=names, verify_integrity=False
996 )
997
998 new_index = indexes[0]
999 n = len(new_index)
1000 kpieces = len(indexes)
1001
1002 # also copies
1003 new_names = list(names)
1004 new_levels = list(levels)
1005
1006 # construct codes
1007 new_codes = []
1008
1009 # do something a bit more speedy
1010
1011 for hlevel, level in zip(zipped, levels, strict=True):
1012 hlevel_index = ensure_index(hlevel)
1013 # GH#64825 get_indexer_for (not get_indexer) so that an overlapping
1014 # IntervalIndex level, which is not unique-as-index, still resolves.
1015 mapped = level.get_indexer_for(hlevel_index)
1016
1017 mask = mapped == -1
1018 if mask.any():
1019 raise ValueError(
1020 f"Values not found in passed level: {hlevel_index[mask]!s}"
1021 )
1022
1023 new_codes.append(np.repeat(mapped, n))
1024
1025 if isinstance(new_index, MultiIndex):
1026 new_levels.extend(new_index.levels)
1027 new_codes.extend(np.tile(lab, kpieces) for lab in new_index.codes)
1028 else:
1029 unique_index = new_index.unique()
1030 new_levels.append(unique_index)
1031 # GH#64825 get_indexer_for so an overlapping IntervalIndex resolves
1032 single_codes = unique_index.get_indexer_for(new_index)
1033 new_codes.append(np.tile(single_codes, kpieces))
1034
1035 if len(new_names) < len(new_levels):
1036 new_names.extend(new_index.names)
1037
1038 return MultiIndex(
1039 levels=new_levels, codes=new_codes, names=new_names, verify_integrity=False
1040 )