1"""
2Functions for preparing various inputs passed to the DataFrame or Series
3constructors before passing them to a BlockManager.
4"""
5
6from __future__ import annotations
7
8from collections import abc
9from typing import (
10 TYPE_CHECKING,
11 Any,
12)
13
14import numpy as np
15from numpy import ma
16
17from pandas._config import using_string_dtype
18
19from pandas._libs import lib
20
21from pandas.core.dtypes.astype import astype_is_view
22from pandas.core.dtypes.cast import (
23 construct_1d_arraylike_from_scalar,
24 dict_compat,
25 maybe_cast_to_datetime,
26 maybe_convert_platform,
27)
28from pandas.core.dtypes.common import (
29 is_1d_only_ea_dtype,
30 is_integer_dtype,
31 is_list_like,
32 is_named_tuple,
33 is_object_dtype,
34 is_scalar,
35)
36from pandas.core.dtypes.dtypes import (
37 BaseMaskedDtype,
38 ExtensionDtype,
39)
40from pandas.core.dtypes.generic import (
41 ABCDataFrame,
42 ABCSeries,
43)
44from pandas.core.dtypes.missing import isna
45
46from pandas.core import (
47 algorithms,
48 common as com,
49)
50from pandas.core.arrays import ExtensionArray
51from pandas.core.arrays.string_ import StringDtype
52from pandas.core.construction import (
53 array as pd_array,
54 extract_array,
55 range_to_ndarray,
56 sanitize_array,
57)
58from pandas.core.indexes.api import (
59 DatetimeIndex,
60 Index,
61 MultiIndex,
62 TimedeltaIndex,
63 default_index,
64 ensure_index,
65 get_objs_combined_axis,
66 maybe_sequence_to_range,
67 union_indexes,
68)
69from pandas.core.internals.blocks import (
70 BlockPlacement,
71 ensure_block_shape,
72 new_block,
73 new_block_2d,
74)
75from pandas.core.internals.managers import (
76 create_block_manager_from_blocks,
77 create_block_manager_from_column_arrays,
78)
79
80if TYPE_CHECKING:
81 from collections.abc import (
82 Hashable,
83 Sequence,
84 )
85
86 from pandas._typing import (
87 ArrayLike,
88 DtypeObj,
89 Manager,
90 npt,
91 )
92# ---------------------------------------------------------------------
93# BlockManager Interface
94
95
96def arrays_to_mgr(
97 arrays,
98 columns: Index,
99 index,
100 *,
101 dtype: DtypeObj | None = None,
102 verify_integrity: bool = True,
103 consolidate: bool = True,
104) -> Manager:
105 """
106 Segregate Series based on type and coerce into matrices.
107
108 Needs to handle a lot of exceptional cases.
109 """
110 if verify_integrity:
111 # figure out the index, if necessary
112 if index is None:
113 index = _extract_index(arrays)
114 else:
115 index = ensure_index(index)
116
117 # don't force copy because getting jammed in an ndarray anyway
118 arrays, refs = _homogenize(arrays, index, dtype)
119 # _homogenize ensures
120 # - all(len(x) == len(index) for x in arrays)
121 # - all(x.ndim == 1 for x in arrays)
122 # - all(isinstance(x, (np.ndarray, ExtensionArray)) for x in arrays)
123 # - all(type(x) is not NumpyExtensionArray for x in arrays)
124
125 else:
126 index = ensure_index(index)
127 arrays = [extract_array(x, extract_numpy=True) for x in arrays]
128 # with _from_arrays, the passed arrays should never be Series objects
129 refs = [None] * len(arrays)
130
131 # Reached via DataFrame._from_arrays; we do minimal validation here
132 for arr in arrays:
133 if (
134 not isinstance(arr, (np.ndarray, ExtensionArray))
135 or arr.ndim != 1
136 or len(arr) != len(index)
137 ):
138 raise ValueError(
139 "Arrays must be 1-dimensional np.ndarray or ExtensionArray "
140 "with length matching len(index)"
141 )
142
143 columns = ensure_index(columns)
144 if len(columns) != len(arrays):
145 raise ValueError("len(arrays) must match len(columns)")
146
147 # from BlockManager perspective
148 axes = [columns, index]
149
150 return create_block_manager_from_column_arrays(
151 arrays, axes, consolidate=consolidate, refs=refs
152 )
153
154
155def rec_array_to_mgr(
156 data: np.rec.recarray | np.ndarray,
157 index,
158 columns,
159 dtype: DtypeObj | None,
160 copy: bool,
161) -> Manager:
162 """
163 Extract from a masked rec array and create the manager.
164 """
165 # essentially process a record array then fill it
166 fdata = ma.getdata(data)
167 if index is None:
168 index = default_index(len(fdata))
169 else:
170 index = ensure_index(index)
171
172 if columns is not None:
173 columns = ensure_index(columns)
174 arrays, arr_columns = to_arrays(fdata, columns)
175
176 # create the manager
177
178 arrays, arr_columns = reorder_arrays(arrays, arr_columns, columns, len(index))
179 if columns is None:
180 columns = arr_columns
181
182 mgr = arrays_to_mgr(arrays, columns, index, dtype=dtype)
183
184 if copy:
185 mgr = mgr.copy(deep=True)
186 return mgr
187
188
189# ---------------------------------------------------------------------
190# DataFrame Constructor Interface
191
192
193def ndarray_to_mgr(
194 values, index, columns, dtype: DtypeObj | None, copy: bool
195) -> Manager:
196 # used in DataFrame.__init__
197 # input must be an ndarray, list, Series, Index, ExtensionArray
198 infer_object = not isinstance(values, (ABCSeries, Index, ExtensionArray))
199
200 if isinstance(values, ABCSeries):
201 if columns is None:
202 if values.name is not None:
203 columns = Index([values.name])
204 if index is None:
205 index = values.index
206 else:
207 values = values.reindex(index)
208
209 # zero len case (GH #2234)
210 if not len(values) and columns is not None and len(columns):
211 values = np.empty((0, 1), dtype=object)
212
213 vdtype = getattr(values, "dtype", None)
214 refs = None
215 if is_1d_only_ea_dtype(vdtype) or is_1d_only_ea_dtype(dtype):
216 # GH#19157
217
218 if isinstance(values, (np.ndarray, ExtensionArray)) and values.ndim > 1:
219 # GH#12513 an EA dtype passed with a 2D array, split into
220 # multiple EAs that view the values
221 # error: No overload variant of "__getitem__" of "ExtensionArray"
222 # matches argument type "Tuple[slice, int]"
223 values = [
224 values[:, n] # type: ignore[call-overload]
225 for n in range(values.shape[1])
226 ]
227 else:
228 values = [values]
229
230 # Handle copy semantics: already copy 1d-only EA. Other arrays will
231 # be copied when consolidating the blocks
232 if copy:
233 values = [
234 (x.copy(deep=True) if isinstance(x, Index) else x.copy())
235 if isinstance(x, (ExtensionArray, Index, ABCSeries))
236 and is_1d_only_ea_dtype(x.dtype)
237 else x
238 for x in values
239 ]
240
241 if columns is None:
242 columns = Index(range(len(values)))
243 else:
244 columns = ensure_index(columns)
245
246 return arrays_to_mgr(values, columns, index, dtype=dtype, consolidate=copy)
247
248 if isinstance(values, (ABCSeries, Index)):
249 if not copy and (dtype is None or astype_is_view(values.dtype, dtype)):
250 refs = values._references
251
252 if isinstance(vdtype, ExtensionDtype):
253 # i.e. Datetime64TZ, PeriodDtype; cases with is_1d_only_ea_dtype(vdtype)
254 # are already caught above
255 values = extract_array(values, extract_numpy=True)
256 if copy:
257 values = values.copy()
258 if values.ndim == 1:
259 values = values.reshape(-1, 1)
260
261 elif isinstance(values, (ABCSeries, Index)):
262 if copy:
263 values = values._values.copy()
264 else:
265 values = values._values
266
267 values = _ensure_2d(values)
268
269 elif isinstance(values, (np.ndarray, ExtensionArray)):
270 # drop subclass info
271 if copy and (dtype is None or astype_is_view(values.dtype, dtype)):
272 # only force a copy now if copy=True was requested
273 # and a subsequent `astype` will not already result in a copy
274 values = np.array(values, copy=True, order="F")
275 else:
276 values = np.asarray(values)
277 values = _ensure_2d(values)
278
279 else:
280 # by definition an array here
281 # the dtypes will be coerced to a single dtype
282 values = _prep_ndarraylike(values, copy=copy)
283
284 if dtype is not None and values.dtype != dtype:
285 # GH#40110 see similar check inside sanitize_array
286 values = sanitize_array(
287 values,
288 None,
289 dtype=dtype,
290 copy=copy,
291 allow_2d=True,
292 )
293
294 # _prep_ndarraylike ensures that values.ndim == 2 at this point
295 index, columns = _get_axes(
296 values.shape[0], values.shape[1], index=index, columns=columns
297 )
298
299 _check_values_indices_shape_match(values, index, columns)
300
301 values = values.T
302
303 # if we don't have a dtype specified, then try to convert objects
304 # on the entire block; this is to convert if we have datetimelike's
305 # embedded in an object type
306 if dtype is None and infer_object and is_object_dtype(values.dtype):
307 obj_columns = list(values)
308 maybe_datetime = [
309 lib.maybe_convert_objects(
310 x,
311 # Here we do not convert numeric dtypes, as if we wanted that,
312 # numpy would have done it for us.
313 convert_numeric=False,
314 convert_non_numeric=True,
315 convert_to_nullable_dtype=False,
316 dtype_if_all_nat=np.dtype("M8[s]"),
317 )
318 for x in obj_columns
319 ]
320 # don't convert (and copy) the objects if no type inference occurs
321 if any(x is not y for x, y in zip(obj_columns, maybe_datetime, strict=True)):
322 block_values = [
323 new_block_2d(ensure_block_shape(dval, 2), placement=BlockPlacement(n))
324 for n, dval in enumerate(maybe_datetime)
325 ]
326 else:
327 bp = BlockPlacement(slice(len(columns)))
328 nb = new_block_2d(values, placement=bp, refs=refs)
329 block_values = [nb]
330 elif dtype is None and values.dtype.kind == "U" and using_string_dtype():
331 dtype = StringDtype(na_value=np.nan)
332
333 obj_columns = list(values)
334 block_values = [
335 new_block(
336 dtype.construct_array_type()._from_sequence(data, dtype=dtype),
337 BlockPlacement(slice(i, i + 1)),
338 ndim=2,
339 )
340 for i, data in enumerate(obj_columns)
341 ]
342
343 else:
344 bp = BlockPlacement(slice(len(columns)))
345 nb = new_block_2d(values, placement=bp, refs=refs)
346 block_values = [nb]
347
348 if len(columns) == 0:
349 # TODO: check len(values) == 0?
350 block_values = []
351
352 return create_block_manager_from_blocks(
353 block_values, [columns, index], verify_integrity=False
354 )
355
356
357def _check_values_indices_shape_match(
358 values: np.ndarray, index: Index, columns: Index
359) -> None:
360 """
361 Check that the shape implied by our axes matches the actual shape of the
362 data.
363 """
364 if values.shape[1] != len(columns) or values.shape[0] != len(index):
365 # Could let this raise in Block constructor, but we get a more
366 # helpful exception message this way.
367 if values.shape[0] == 0 < len(index):
368 raise ValueError("Empty data passed with indices specified.")
369
370 passed = values.shape
371 implied = (len(index), len(columns))
372 raise ValueError(f"Shape of passed values is {passed}, indices imply {implied}")
373
374
375def dict_to_mgr(
376 data: dict,
377 index,
378 columns,
379 *,
380 dtype: DtypeObj | None = None,
381 copy: bool = True,
382) -> Manager:
383 """
384 Segregate Series based on type and coerce into matrices.
385 Needs to handle a lot of exceptional cases.
386
387 Used in DataFrame.__init__
388 """
389 arrays: Sequence[Any]
390
391 if columns is not None:
392 columns = ensure_index(columns)
393 if dtype is not None and not isinstance(dtype, np.dtype):
394 # e.g. test_dataframe_from_dict_of_series
395 arrays = [dtype.na_value] * len(columns)
396 else:
397 arrays = [np.nan] * len(columns)
398 midxs = set()
399 data_keys = ensure_index(data.keys()) # type: ignore[arg-type]
400 data_values = list(data.values())
401
402 for i, column in enumerate(columns):
403 try:
404 idx = data_keys.get_loc(column)
405 except KeyError:
406 midxs.add(i)
407 continue
408 array = data_values[idx]
409 arrays[i] = array
410 if is_scalar(array) and isna(array):
411 midxs.add(i)
412
413 if index is None:
414 # GH10856
415 # raise ValueError if only scalars in dict
416 if midxs:
417 index = _extract_index(
418 [array for i, array in enumerate(arrays) if i not in midxs]
419 )
420 else:
421 index = _extract_index(arrays)
422 else:
423 index = ensure_index(index)
424
425 # no obvious "empty" int column
426 if midxs and not is_integer_dtype(dtype):
427 # GH#1783
428 for i in midxs:
429 arr = construct_1d_arraylike_from_scalar(
430 arrays[i],
431 len(index),
432 dtype if dtype is not None else np.dtype("object"),
433 )
434 arrays[i] = arr
435
436 else:
437 keys = maybe_sequence_to_range(list(data.keys()))
438 columns = Index(keys) if keys else default_index(0)
439 arrays = [com.maybe_iterable_to_list(data[k]) for k in keys]
440
441 if copy:
442 # We only need to copy arrays that will not get consolidated, i.e.
443 # only EA arrays
444 arrays = [
445 (
446 x.copy()
447 if isinstance(x, ExtensionArray)
448 else (
449 x.copy(deep=True)
450 if (
451 isinstance(x, Index)
452 or (isinstance(x, ABCSeries) and is_1d_only_ea_dtype(x.dtype))
453 )
454 else x
455 )
456 )
457 for x in arrays
458 ]
459
460 return arrays_to_mgr(arrays, columns, index, dtype=dtype, consolidate=copy)
461
462
463def nested_data_to_arrays(
464 data: Sequence,
465 columns: Index | None,
466 index: Index | None,
467 dtype: DtypeObj | None,
468) -> tuple[list[ArrayLike], Index, Index]:
469 """
470 Convert a single sequence of arrays to multiple arrays.
471 """
472 # By the time we get here we have already checked treat_as_nested(data)
473
474 if is_named_tuple(data[0]) and columns is None:
475 columns = ensure_index(data[0]._fields)
476
477 arrays, columns = to_arrays(data, columns, dtype=dtype)
478 columns = ensure_index(columns)
479
480 if index is None:
481 if isinstance(data[0], ABCSeries):
482 index = _get_names_from_index(data)
483 else:
484 index = default_index(len(data))
485
486 return arrays, columns, index
487
488
489def treat_as_nested(data) -> bool:
490 """
491 Check if we should use nested_data_to_arrays.
492 """
493 return (
494 len(data) > 0
495 and is_list_like(data[0])
496 and getattr(data[0], "ndim", 1) == 1
497 and not (isinstance(data, ExtensionArray) and data.ndim == 2)
498 )
499
500
501# ---------------------------------------------------------------------
502
503
504def _prep_ndarraylike(values, copy: bool = True) -> np.ndarray:
505 # values is specifically _not_ ndarray, EA, Index, or Series
506 # We only get here with `not treat_as_nested(values)`
507
508 if len(values) == 0:
509 # TODO: check for length-zero range, in which case return int64 dtype?
510 # TODO: reuse anything in try_cast?
511 return np.empty((0, 0), dtype=object)
512 elif isinstance(values, range):
513 arr = range_to_ndarray(values)
514 return arr[..., np.newaxis]
515
516 def convert(v):
517 if not is_list_like(v) or isinstance(v, ABCDataFrame):
518 return v
519
520 v = extract_array(v, extract_numpy=True)
521 res = maybe_convert_platform(v)
522 # We don't do maybe_infer_objects here bc we will end up doing
523 # it column-by-column in ndarray_to_mgr
524 return res
525
526 # we could have a 1-dim or 2-dim list here
527 # this is equiv of np.asarray, but does object conversion
528 # and platform dtype preservation
529 # does not convert e.g. [1, "a", True] to ["1", "a", "True"] like
530 # np.asarray would
531 if is_list_like(values[0]):
532 values = np.array([convert(v) for v in values])
533 elif isinstance(values[0], np.ndarray) and values[0].ndim == 0:
534 # GH#21861 see test_constructor_list_of_lists
535 values = np.array([convert(v) for v in values])
536 else:
537 values = convert(values)
538
539 return _ensure_2d(values)
540
541
542def _ensure_2d(values: np.ndarray) -> np.ndarray:
543 """
544 Reshape 1D values, raise on anything else other than 2D.
545 """
546 if values.ndim == 1:
547 values = values.reshape((values.shape[0], 1))
548 elif values.ndim != 2:
549 raise ValueError(f"Must pass 2-d input. shape={values.shape}")
550 return values
551
552
553def _homogenize(
554 data, index: Index, dtype: DtypeObj | None
555) -> tuple[list[ArrayLike], list[Any]]:
556 oindex = None
557 homogenized = []
558 # if the original array-like in `data` is a Series, keep track of this Series' refs
559 refs: list[Any] = []
560
561 for val in data:
562 if isinstance(val, (ABCSeries, Index)):
563 if dtype is not None:
564 val = val.astype(dtype)
565 if isinstance(val, ABCSeries) and val.index is not index:
566 # Forces alignment. No need to copy data since we
567 # are putting it into an ndarray later
568 val = val.reindex(index)
569 refs.append(val._references)
570 val = val._values
571 else:
572 if isinstance(val, dict):
573 # GH#41785 this _should_ be equivalent to (but faster than)
574 # val = Series(val, index=index)._values
575 if oindex is None:
576 oindex = index.astype("O")
577
578 if isinstance(index, (DatetimeIndex, TimedeltaIndex)):
579 # see test_constructor_dict_datetime64_index
580 val = dict_compat(val)
581 else:
582 # see test_constructor_subclass_dict
583 val = dict(val)
584
585 if not isinstance(index, MultiIndex) and index.hasnans:
586 # GH#63889 Check if dict has missing value keys that need special
587 # handling (i.e. None/np.nan/pd.NA might no longer be matched
588 # when using fast_multiget with processed object index values)
589 from pandas import Series
590
591 val = Series(val).reindex(index)._values
592 else:
593 # Fast path: use lib.fast_multiget for dicts without missing keys
594 val = lib.fast_multiget(val, oindex._values, default=np.nan)
595
596 val = sanitize_array(val, index, dtype=dtype, copy=False)
597 com.require_length_match(val, index)
598 refs.append(None)
599
600 homogenized.append(val)
601
602 return homogenized, refs
603
604
605def _extract_index(data) -> Index:
606 """
607 Try to infer an Index from the passed data, raise ValueError on failure.
608 """
609 index: Index
610 if len(data) == 0:
611 return default_index(0)
612
613 raw_lengths = set()
614 indexes: list[list[Hashable] | Index] = []
615
616 have_raw_arrays = False
617 have_series = False
618 have_dicts = False
619
620 for val in data:
621 if isinstance(val, ABCSeries):
622 have_series = True
623 indexes.append(val.index)
624 elif isinstance(val, dict):
625 have_dicts = True
626 indexes.append(list(val.keys()))
627 elif is_list_like(val) and getattr(val, "ndim", 1) == 1:
628 have_raw_arrays = True
629 raw_lengths.add(len(val))
630 elif isinstance(val, np.ndarray) and val.ndim > 1:
631 raise ValueError("Per-column arrays must each be 1-dimensional")
632
633 if not indexes and not raw_lengths:
634 raise ValueError("If using all scalar values, you must pass an index")
635
636 if have_series:
637 index = union_indexes(indexes)
638 elif have_dicts:
639 index = union_indexes(indexes, sort=False)
640
641 if have_raw_arrays:
642 if len(raw_lengths) > 1:
643 raise ValueError("All arrays must be of the same length")
644
645 if have_dicts:
646 raise ValueError(
647 "Mixing dicts with non-Series may lead to ambiguous ordering."
648 )
649 raw_length = raw_lengths.pop()
650 if have_series:
651 if raw_length != len(index):
652 msg = (
653 f"array length {raw_length} does not match index "
654 f"length {len(index)}"
655 )
656 raise ValueError(msg)
657 else:
658 index = default_index(raw_length)
659
660 return ensure_index(index)
661
662
663def reorder_arrays(
664 arrays: list[ArrayLike], arr_columns: Index, columns: Index | None, length: int
665) -> tuple[list[ArrayLike], Index]:
666 """
667 Preemptively (cheaply) reindex arrays with new columns.
668 """
669 # reorder according to the columns
670 if columns is not None:
671 if not columns.equals(arr_columns):
672 # if they are equal, there is nothing to do
673 new_arrays: list[ArrayLike] = []
674 indexer = arr_columns.get_indexer(columns)
675 for i, k in enumerate(indexer):
676 if k == -1:
677 # by convention default is all-NaN object dtype
678 arr = np.empty(length, dtype=object)
679 arr.fill(np.nan)
680 else:
681 arr = arrays[k]
682 new_arrays.append(arr)
683
684 arrays = new_arrays
685 arr_columns = columns
686
687 return arrays, arr_columns
688
689
690def _get_names_from_index(data) -> Index:
691 has_some_name = any(getattr(s, "name", None) is not None for s in data)
692 if not has_some_name:
693 return default_index(len(data))
694
695 index: list[Hashable] = list(range(len(data)))
696 count = 0
697 for i, s in enumerate(data):
698 n = getattr(s, "name", None)
699 if n is not None:
700 index[i] = n
701 else:
702 index[i] = f"Unnamed {count}"
703 count += 1
704
705 return Index(index)
706
707
708def _get_axes(
709 N: int, K: int, index: Index | None, columns: Index | None
710) -> tuple[Index, Index]:
711 # helper to create the axes as indexes
712 # return axes or defaults
713
714 if index is None:
715 index = default_index(N)
716 else:
717 index = ensure_index(index)
718
719 if columns is None:
720 columns = default_index(K)
721 else:
722 columns = ensure_index(columns)
723 return index, columns
724
725
726def dataclasses_to_dicts(data):
727 """
728 Converts a list of dataclass instances to a list of dictionaries.
729
730 Parameters
731 ----------
732 data : List[Type[dataclass]]
733
734 Returns
735 --------
736 list_dict : List[dict]
737
738 Examples
739 --------
740 >>> from dataclasses import dataclass
741 >>> @dataclass
742 ... class Point:
743 ... x: int
744 ... y: int
745
746 >>> dataclasses_to_dicts([Point(1, 2), Point(2, 3)])
747 [{'x': 1, 'y': 2}, {'x': 2, 'y': 3}]
748
749 """
750 from dataclasses import asdict
751
752 return list(map(asdict, data))
753
754
755# ---------------------------------------------------------------------
756# Conversion of Inputs to Arrays
757
758
759def to_arrays(
760 data, columns: Index | None, dtype: DtypeObj | None = None
761) -> tuple[list[ArrayLike], Index]:
762 """
763 Return list of arrays, columns.
764
765 Returns
766 -------
767 list[ArrayLike]
768 These will become columns in a DataFrame.
769 Index
770 This will become frame.columns.
771
772 Notes
773 -----
774 Ensures that len(result_arrays) == len(result_index).
775 """
776
777 if not len(data):
778 if isinstance(data, np.ndarray):
779 if data.dtype.names is not None:
780 # i.e. numpy structured array
781 columns = ensure_index(data.dtype.names)
782 arrays = [data[name] for name in columns]
783
784 if len(data) == 0:
785 # GH#42456 the indexing above results in list of 2D ndarrays
786 # TODO: is that an issue with numpy?
787 for i, arr in enumerate(arrays):
788 if arr.ndim == 2:
789 arrays[i] = arr[:, 0]
790
791 return arrays, columns
792 return [], ensure_index([])
793
794 elif isinstance(data, np.ndarray) and data.dtype.names is not None:
795 # e.g. recarray
796 if columns is None:
797 columns = Index(data.dtype.names)
798 arrays = [data[k] for k in columns]
799 return arrays, columns
800
801 if isinstance(data[0], (list, tuple)):
802 arr = _list_to_arrays(data)
803 elif isinstance(data[0], abc.Mapping):
804 arr, columns = _list_of_dict_to_arrays(data, columns)
805 elif isinstance(data[0], ABCSeries):
806 arr, columns = _list_of_series_to_arrays(data, columns)
807 else:
808 # last ditch effort
809 data = [tuple(x) for x in data]
810 arr = _list_to_arrays(data)
811
812 content, columns = _finalize_columns_and_data(arr, columns, dtype)
813 return content, columns
814
815
816def _list_to_arrays(data: list[tuple | list]) -> np.ndarray:
817 # Returned np.ndarray has ndim = 2
818 # Note: we already check len(data) > 0 before getting hre
819 if isinstance(data[0], tuple):
820 content = lib.to_object_array_tuples(data)
821 else:
822 # list of lists
823 content = lib.to_object_array(data)
824 return content
825
826
827def _list_of_series_to_arrays(
828 data: list,
829 columns: Index | None,
830) -> tuple[np.ndarray, Index]:
831 # returned np.ndarray has ndim == 2
832
833 if columns is None:
834 # We know pass_data is non-empty because data[0] is a Series
835 pass_data = [x for x in data if isinstance(x, (ABCSeries, ABCDataFrame))]
836 columns = get_objs_combined_axis(pass_data, sort=False)
837
838 indexer_cache: dict[int, np.ndarray] = {}
839
840 aligned_values = []
841 for s in data:
842 index = getattr(s, "index", None)
843 if index is None:
844 index = default_index(len(s))
845
846 if id(index) in indexer_cache:
847 indexer = indexer_cache[id(index)]
848 else:
849 indexer = indexer_cache[id(index)] = index.get_indexer(columns)
850
851 values = extract_array(s, extract_numpy=True)
852 aligned_values.append(algorithms.take_nd(values, indexer))
853
854 content = np.vstack(aligned_values)
855 return content, columns
856
857
858def _list_of_dict_to_arrays(
859 data: list[dict],
860 columns: Index | None,
861) -> tuple[np.ndarray, Index]:
862 """
863 Convert list of dicts to numpy arrays
864
865 if `columns` is not passed, column names are inferred from the records
866 - for OrderedDict and dicts, the column names match
867 the key insertion-order from the first record to the last.
868 - For other kinds of dict-likes, the keys are lexically sorted.
869
870 Parameters
871 ----------
872 data : iterable
873 collection of records (OrderedDict, dict)
874 columns: iterables or None
875
876 Returns
877 -------
878 content : np.ndarray[object, ndim=2]
879 columns : Index
880 """
881 # assure that they are of the base dict class and not of derived
882 # classes
883 data = [d if type(d) is dict else dict(d) for d in data]
884
885 if columns is None:
886 gen = (list(x.keys()) for x in data)
887 sort = not any(isinstance(d, dict) for d in data)
888 pre_cols = lib.fast_unique_multiple_list_gen(gen, sort=sort)
889 columns = ensure_index(pre_cols)
890
891 # use pre_cols to preserve exact values that were present as dict keys
892 # (e.g. otherwise missing values might be coerced to the canonical repr)
893 content = lib.dicts_to_array(data, pre_cols)
894 else:
895 content = lib.dicts_to_array(data, list(columns))
896
897 return content, columns
898
899
900def _finalize_columns_and_data(
901 content: np.ndarray, # ndim == 2
902 columns: Index | None,
903 dtype: DtypeObj | None,
904) -> tuple[list[ArrayLike], Index]:
905 """
906 Ensure we have valid columns, cast object dtypes if possible.
907 """
908 contents = list(content.T)
909
910 try:
911 columns = _validate_or_indexify_columns(contents, columns)
912 except AssertionError as err:
913 # GH#26429 do not raise user-facing AssertionError
914 raise ValueError(err) from err
915
916 if contents and contents[0].dtype == np.object_:
917 contents = convert_object_array(contents, dtype=dtype)
918
919 return contents, columns
920
921
922def _validate_or_indexify_columns(
923 content: list[np.ndarray], columns: Index | None
924) -> Index:
925 """
926 If columns is None, make numbers as column names; Otherwise, validate that
927 columns have valid length.
928
929 Parameters
930 ----------
931 content : list of np.ndarrays
932 columns : Index or None
933
934 Returns
935 -------
936 Index
937 If columns is None, assign positional column index value as columns.
938
939 Raises
940 ------
941 1. AssertionError when content is not composed of list of lists, and if
942 length of columns is not equal to length of content.
943 2. ValueError when content is list of lists, but length of each sub-list
944 is not equal
945 3. ValueError when content is list of lists, but length of sub-list is
946 not equal to length of content
947 """
948 if columns is None:
949 columns = default_index(len(content))
950 else:
951 # Add mask for data which is composed of list of lists
952 is_mi_list = isinstance(columns, list) and all(
953 isinstance(col, list) for col in columns
954 )
955
956 if not is_mi_list and len(columns) != len(content): # pragma: no cover
957 # caller's responsibility to check for this...
958 raise AssertionError(
959 f"{len(columns)} columns passed, passed data had {len(content)} columns"
960 )
961 if is_mi_list:
962 # check if nested list column, length of each sub-list should be equal
963 if len({len(col) for col in columns}) > 1:
964 raise ValueError(
965 "Length of columns passed for MultiIndex columns is different"
966 )
967
968 # if columns is not empty and length of sublist is not equal to content
969 if columns and len(columns[0]) != len(content):
970 raise ValueError(
971 f"{len(columns[0])} columns passed, passed data had "
972 f"{len(content)} columns"
973 )
974 return columns
975
976
977def convert_object_array(
978 content: list[npt.NDArray[np.object_]],
979 dtype: DtypeObj | None,
980 dtype_backend: str = "numpy",
981 coerce_float: bool = False,
982) -> list[ArrayLike]:
983 """
984 Internal function to convert object array.
985
986 Parameters
987 ----------
988 content: List[np.ndarray]
989 dtype: np.dtype or ExtensionDtype
990 dtype_backend: Controls if nullable/pyarrow dtypes are returned.
991 coerce_float: Cast floats that are integers to int.
992
993 Returns
994 -------
995 List[ArrayLike]
996 """
997 # provide soft conversion of object dtypes
998
999 def convert(arr):
1000 if dtype != np.dtype("O"):
1001 # e.g. if dtype is UInt32 then we want to cast Nones to NA instead of
1002 # NaN in maybe_convert_objects.
1003 to_nullable = dtype_backend != "numpy" or isinstance(dtype, BaseMaskedDtype)
1004 arr = lib.maybe_convert_objects(
1005 arr,
1006 try_float=coerce_float,
1007 convert_to_nullable_dtype=to_nullable,
1008 )
1009 # Notes on cases that get here 2023-02-15
1010 # 1) we DO get here when arr is all Timestamps and dtype=None
1011 # 2) disabling this doesn't break the world, so this must be
1012 # getting caught at a higher level
1013 # 3) passing convert_non_numeric to maybe_convert_objects get this right
1014 # 4) convert_non_numeric?
1015
1016 if dtype is None:
1017 if arr.dtype == np.dtype("O"):
1018 # i.e. maybe_convert_objects didn't convert
1019 convert_to_nullable_dtype = dtype_backend != "numpy"
1020 arr = lib.maybe_convert_objects(
1021 arr,
1022 # Here we do not convert numeric dtypes, as if we wanted that,
1023 # numpy would have done it for us.
1024 convert_numeric=False,
1025 convert_non_numeric=True,
1026 convert_to_nullable_dtype=convert_to_nullable_dtype,
1027 dtype_if_all_nat=np.dtype("M8[s]"),
1028 )
1029 if convert_to_nullable_dtype and arr.dtype == np.dtype("O"):
1030 new_dtype = StringDtype()
1031 arr_cls = new_dtype.construct_array_type()
1032 arr = arr_cls._from_sequence(arr, dtype=new_dtype)
1033 elif dtype_backend != "numpy" and isinstance(arr, np.ndarray):
1034 if arr.dtype.kind in "iufb":
1035 arr = pd_array(arr, copy=False)
1036
1037 elif isinstance(dtype, ExtensionDtype):
1038 # TODO: test(s) that get here
1039 # TODO: try to de-duplicate this convert function with
1040 # core.construction functions
1041 cls = dtype.construct_array_type()
1042 arr = cls._from_sequence(arr, dtype=dtype, copy=False)
1043 elif dtype.kind in "mM":
1044 # This restriction is harmless bc these are the only cases
1045 # where maybe_cast_to_datetime is not a no-op.
1046 # Here we know:
1047 # 1) dtype.kind in "mM" and
1048 # 2) arr is either object or numeric dtype
1049 arr = maybe_cast_to_datetime(arr, dtype)
1050
1051 return arr
1052
1053 arrays = [convert(arr) for arr in content]
1054
1055 return arrays