1from __future__ import annotations
2
3from abc import (
4 ABC,
5 abstractmethod,
6)
7import sys
8from textwrap import dedent
9from typing import TYPE_CHECKING
10
11from pandas._config import get_option
12
13from pandas.io.formats import format as fmt
14from pandas.io.formats.printing import pprint_thing
15
16if TYPE_CHECKING:
17 from collections.abc import (
18 Iterable,
19 Iterator,
20 Mapping,
21 Sequence,
22 )
23
24 from pandas._typing import (
25 Dtype,
26 WriteBuffer,
27 )
28
29 from pandas import (
30 DataFrame,
31 Index,
32 Series,
33 )
34
35show_counts_sub = dedent(
36 """\
37 show_counts : bool, optional
38 Whether to show the non-null counts. By default, this is shown
39 only if the DataFrame is smaller than
40 ``pandas.options.display.max_info_rows`` and
41 ``pandas.options.display.max_info_columns``. A value of True always
42 shows the counts, and False never shows the counts."""
43)
44
45series_examples_sub = dedent(
46 """\
47 >>> int_values = [1, 2, 3, 4, 5]
48 >>> text_values = ['alpha', 'beta', 'gamma', 'delta', 'epsilon']
49 >>> s = pd.Series(text_values, index=int_values)
50 >>> s.info()
51 <class 'pandas.Series'>
52 Index: 5 entries, 1 to 5
53 Series name: None
54 Non-Null Count Dtype
55 -------------- -----
56 5 non-null object
57 dtypes: object(1)
58 memory usage: 80.0+ bytes
59
60 Prints a summary excluding information about its values:
61
62 >>> s.info(verbose=False)
63 <class 'pandas.Series'>
64 Index: 5 entries, 1 to 5
65 dtypes: object(1)
66 memory usage: 80.0+ bytes
67
68 Pipe output of Series.info to buffer instead of sys.stdout, get
69 buffer content and writes to a text file:
70
71 >>> import io
72 >>> buffer = io.StringIO()
73 >>> s.info(buf=buffer)
74 >>> s = buffer.getvalue()
75 >>> with open("df_info.txt", "w",
76 ... encoding="utf-8") as f: # doctest: +SKIP
77 ... f.write(s)
78 260
79
80 The `memory_usage` parameter allows deep introspection mode, specially
81 useful for big Series and fine-tune memory optimization:
82
83 >>> random_strings_array = np.random.choice(['a', 'b', 'c'], 10 ** 6)
84 >>> s = pd.Series(np.random.choice(['a', 'b', 'c'], 10 ** 6))
85 >>> s.info()
86 <class 'pandas.Series'>
87 RangeIndex: 1000000 entries, 0 to 999999
88 Series name: None
89 Non-Null Count Dtype
90 -------------- -----
91 1000000 non-null object
92 dtypes: object(1)
93 memory usage: 7.6+ MB
94
95 >>> s.info(memory_usage='deep')
96 <class 'pandas.Series'>
97 RangeIndex: 1000000 entries, 0 to 999999
98 Series name: None
99 Non-Null Count Dtype
100 -------------- -----
101 1000000 non-null object
102 dtypes: object(1)
103 memory usage: 55.3 MB"""
104)
105
106
107series_see_also_sub = dedent(
108 """\
109 Series.describe: Generate descriptive statistics of Series.
110 Series.memory_usage: Memory usage of Series."""
111)
112series_max_cols_sub = dedent(
113 """\
114 max_cols : int, optional
115 Unused, exists only for compatibility with DataFrame.info."""
116)
117
118
119series_sub_kwargs = {
120 "klass": "Series",
121 "type_sub": "",
122 "max_cols_sub": series_max_cols_sub,
123 "show_counts_sub": show_counts_sub,
124 "examples_sub": series_examples_sub,
125 "see_also_sub": series_see_also_sub,
126 "version_added_sub": "\n.. versionadded:: 1.4.0\n",
127}
128
129
130def _put_str(s: str | Dtype, space: int) -> str:
131 """
132 Make string of specified length, padding to the right if necessary.
133
134 Parameters
135 ----------
136 s : Union[str, Dtype]
137 String to be formatted.
138 space : int
139 Length to force string to be of.
140
141 Returns
142 -------
143 str
144 String coerced to given length.
145
146 Examples
147 --------
148 >>> pd.io.formats.info._put_str("panda", 6)
149 'panda '
150 >>> pd.io.formats.info._put_str("panda", 4)
151 'pand'
152 """
153 return str(s)[:space].ljust(space)
154
155
156def _sizeof_fmt(num: float, size_qualifier: str) -> str:
157 """
158 Return size in human readable format.
159
160 Parameters
161 ----------
162 num : int
163 Size in bytes.
164 size_qualifier : str
165 Either empty, or '+' (if lower bound).
166
167 Returns
168 -------
169 str
170 Size in human readable format.
171
172 Examples
173 --------
174 >>> _sizeof_fmt(23028, "")
175 '22.5 KB'
176
177 >>> _sizeof_fmt(23028, "+")
178 '22.5+ KB'
179 """
180 for x in ["bytes", "KB", "MB", "GB", "TB"]:
181 if num < 1024.0:
182 return f"{num:3.1f}{size_qualifier} {x}"
183 num /= 1024.0
184 return f"{num:3.1f}{size_qualifier} PB"
185
186
187def _initialize_memory_usage(
188 memory_usage: bool | str | None = None,
189) -> bool | str:
190 """Get memory usage based on inputs and display options."""
191 if memory_usage is None:
192 memory_usage = get_option("display.memory_usage")
193 return memory_usage
194
195
196class _BaseInfo(ABC):
197 """
198 Base class for DataFrameInfo and SeriesInfo.
199
200 Parameters
201 ----------
202 data : DataFrame or Series
203 Either dataframe or series.
204 memory_usage : bool or str, optional
205 If "deep", introspect the data deeply by interrogating object dtypes
206 for system-level memory consumption, and include it in the returned
207 values.
208 """
209
210 data: DataFrame | Series
211 memory_usage: bool | str
212
213 @property
214 @abstractmethod
215 def dtypes(self) -> Iterable[Dtype]:
216 """
217 Dtypes.
218
219 Returns
220 -------
221 dtypes : sequence
222 Dtype of each of the DataFrame's columns (or one series column).
223 """
224
225 @property
226 @abstractmethod
227 def dtype_counts(self) -> Mapping[str, int]:
228 """Mapping dtype - number of counts."""
229
230 @property
231 @abstractmethod
232 def non_null_counts(self) -> list[int] | Series:
233 """Sequence of non-null counts for all columns or column (if series)."""
234
235 @property
236 @abstractmethod
237 def memory_usage_bytes(self) -> int:
238 """
239 Memory usage in bytes.
240
241 Returns
242 -------
243 memory_usage_bytes : int
244 Object's total memory usage in bytes.
245 """
246
247 @property
248 def memory_usage_string(self) -> str:
249 """Memory usage in a form of human readable string."""
250 return f"{_sizeof_fmt(self.memory_usage_bytes, self.size_qualifier)}\n"
251
252 @property
253 def size_qualifier(self) -> str:
254 size_qualifier = ""
255 if self.memory_usage:
256 if self.memory_usage != "deep":
257 # size_qualifier is just a best effort; not guaranteed to catch
258 # all cases (e.g., it misses categorical data even with object
259 # categories)
260 if (
261 "object" in self.dtype_counts
262 or self.data.index._is_memory_usage_qualified
263 ):
264 size_qualifier = "+"
265 return size_qualifier
266
267 @abstractmethod
268 def render(
269 self,
270 *,
271 buf: WriteBuffer[str] | None,
272 max_cols: int | None,
273 verbose: bool | None,
274 show_counts: bool | None,
275 ) -> None:
276 pass
277
278
279class DataFrameInfo(_BaseInfo):
280 """
281 Class storing dataframe-specific info.
282 """
283
284 def __init__(
285 self,
286 data: DataFrame,
287 memory_usage: bool | str | None = None,
288 ) -> None:
289 self.data: DataFrame = data
290 self.memory_usage = _initialize_memory_usage(memory_usage)
291
292 @property
293 def dtype_counts(self) -> Mapping[str, int]:
294 return _get_dataframe_dtype_counts(self.data)
295
296 @property
297 def dtypes(self) -> Iterable[Dtype]:
298 """
299 Dtypes.
300
301 Returns
302 -------
303 dtypes
304 Dtype of each of the DataFrame's columns.
305 """
306 return self.data.dtypes
307
308 @property
309 def ids(self) -> Index:
310 """
311 Column names.
312
313 Returns
314 -------
315 ids : Index
316 DataFrame's column names.
317 """
318 return self.data.columns
319
320 @property
321 def col_count(self) -> int:
322 """Number of columns to be summarized."""
323 return len(self.ids)
324
325 @property
326 def non_null_counts(self) -> Series:
327 """Sequence of non-null counts for all columns or column (if series)."""
328 return self.data.count()
329
330 @property
331 def memory_usage_bytes(self) -> int:
332 deep = self.memory_usage == "deep"
333 return self.data.memory_usage(index=True, deep=deep).sum()
334
335 def render(
336 self,
337 *,
338 buf: WriteBuffer[str] | None,
339 max_cols: int | None,
340 verbose: bool | None,
341 show_counts: bool | None,
342 ) -> None:
343 printer = _DataFrameInfoPrinter(
344 info=self,
345 max_cols=max_cols,
346 verbose=verbose,
347 show_counts=show_counts,
348 )
349 printer.to_buffer(buf)
350
351
352class SeriesInfo(_BaseInfo):
353 """
354 Class storing series-specific info.
355 """
356
357 def __init__(
358 self,
359 data: Series,
360 memory_usage: bool | str | None = None,
361 ) -> None:
362 self.data: Series = data
363 self.memory_usage = _initialize_memory_usage(memory_usage)
364
365 def render(
366 self,
367 *,
368 buf: WriteBuffer[str] | None = None,
369 max_cols: int | None = None,
370 verbose: bool | None = None,
371 show_counts: bool | None = None,
372 ) -> None:
373 if max_cols is not None:
374 raise ValueError(
375 "Argument `max_cols` can only be passed "
376 "in DataFrame.info, not Series.info"
377 )
378 printer = _SeriesInfoPrinter(
379 info=self,
380 verbose=verbose,
381 show_counts=show_counts,
382 )
383 printer.to_buffer(buf)
384
385 @property
386 def non_null_counts(self) -> list[int]:
387 return [self.data.count()]
388
389 @property
390 def dtypes(self) -> Iterable[Dtype]:
391 return [self.data.dtypes]
392
393 @property
394 def dtype_counts(self) -> Mapping[str, int]:
395 from pandas.core.frame import DataFrame
396
397 return _get_dataframe_dtype_counts(DataFrame(self.data))
398
399 @property
400 def memory_usage_bytes(self) -> int:
401 """Memory usage in bytes.
402
403 Returns
404 -------
405 memory_usage_bytes : int
406 Object's total memory usage in bytes.
407 """
408 deep = self.memory_usage == "deep"
409 return self.data.memory_usage(index=True, deep=deep)
410
411
412class _InfoPrinterAbstract:
413 """
414 Class for printing dataframe or series info.
415 """
416
417 def to_buffer(self, buf: WriteBuffer[str] | None = None) -> None:
418 """Save dataframe info into buffer."""
419 table_builder = self._create_table_builder()
420 lines = table_builder.get_lines()
421 if buf is None: # pragma: no cover
422 buf = sys.stdout
423 fmt.buffer_put_lines(buf, lines)
424
425 @abstractmethod
426 def _create_table_builder(self) -> _TableBuilderAbstract:
427 """Create instance of table builder."""
428
429
430class _DataFrameInfoPrinter(_InfoPrinterAbstract):
431 """
432 Class for printing dataframe info.
433
434 Parameters
435 ----------
436 info : DataFrameInfo
437 Instance of DataFrameInfo.
438 max_cols : int, optional
439 When to switch from the verbose to the truncated output.
440 verbose : bool, optional
441 Whether to print the full summary.
442 show_counts : bool, optional
443 Whether to show the non-null counts.
444 """
445
446 def __init__(
447 self,
448 info: DataFrameInfo,
449 max_cols: int | None = None,
450 verbose: bool | None = None,
451 show_counts: bool | None = None,
452 ) -> None:
453 self.info = info
454 self.data = info.data
455 self.verbose = verbose
456 self.max_cols = self._initialize_max_cols(max_cols)
457 self.show_counts = self._initialize_show_counts(show_counts)
458
459 @property
460 def max_rows(self) -> int:
461 """Maximum info rows to be displayed."""
462 return get_option("display.max_info_rows")
463
464 @property
465 def exceeds_info_cols(self) -> bool:
466 """Check if number of columns to be summarized does not exceed maximum."""
467 return bool(self.col_count > self.max_cols)
468
469 @property
470 def exceeds_info_rows(self) -> bool:
471 """Check if number of rows to be summarized does not exceed maximum."""
472 return bool(len(self.data) > self.max_rows)
473
474 @property
475 def col_count(self) -> int:
476 """Number of columns to be summarized."""
477 return self.info.col_count
478
479 def _initialize_max_cols(self, max_cols: int | None) -> int:
480 if max_cols is None:
481 return get_option("display.max_info_columns")
482 return max_cols
483
484 def _initialize_show_counts(self, show_counts: bool | None) -> bool:
485 if show_counts is None:
486 return bool(not self.exceeds_info_cols and not self.exceeds_info_rows)
487 else:
488 return show_counts
489
490 def _create_table_builder(self) -> _DataFrameTableBuilder:
491 """
492 Create instance of table builder based on verbosity and display settings.
493 """
494 if self.verbose:
495 return _DataFrameTableBuilderVerbose(
496 info=self.info,
497 with_counts=self.show_counts,
498 )
499 elif self.verbose is False: # specifically set to False, not necessarily None
500 return _DataFrameTableBuilderNonVerbose(info=self.info)
501 elif self.exceeds_info_cols:
502 return _DataFrameTableBuilderNonVerbose(info=self.info)
503 else:
504 return _DataFrameTableBuilderVerbose(
505 info=self.info,
506 with_counts=self.show_counts,
507 )
508
509
510class _SeriesInfoPrinter(_InfoPrinterAbstract):
511 """Class for printing series info.
512
513 Parameters
514 ----------
515 info : SeriesInfo
516 Instance of SeriesInfo.
517 verbose : bool, optional
518 Whether to print the full summary.
519 show_counts : bool, optional
520 Whether to show the non-null counts.
521 """
522
523 def __init__(
524 self,
525 info: SeriesInfo,
526 verbose: bool | None = None,
527 show_counts: bool | None = None,
528 ) -> None:
529 self.info = info
530 self.data = info.data
531 self.verbose = verbose
532 self.show_counts = self._initialize_show_counts(show_counts)
533
534 def _create_table_builder(self) -> _SeriesTableBuilder:
535 """
536 Create instance of table builder based on verbosity.
537 """
538 if self.verbose or self.verbose is None:
539 return _SeriesTableBuilderVerbose(
540 info=self.info,
541 with_counts=self.show_counts,
542 )
543 else:
544 return _SeriesTableBuilderNonVerbose(info=self.info)
545
546 def _initialize_show_counts(self, show_counts: bool | None) -> bool:
547 if show_counts is None:
548 return True
549 else:
550 return show_counts
551
552
553class _TableBuilderAbstract(ABC):
554 """
555 Abstract builder for info table.
556 """
557
558 _lines: list[str]
559 info: _BaseInfo
560
561 @abstractmethod
562 def get_lines(self) -> list[str]:
563 """Product in a form of list of lines (strings)."""
564
565 @property
566 def data(self) -> DataFrame | Series:
567 return self.info.data
568
569 @property
570 def dtypes(self) -> Iterable[Dtype]:
571 """Dtypes of each of the DataFrame's columns."""
572 return self.info.dtypes
573
574 @property
575 def dtype_counts(self) -> Mapping[str, int]:
576 """Mapping dtype - number of counts."""
577 return self.info.dtype_counts
578
579 @property
580 def display_memory_usage(self) -> bool:
581 """Whether to display memory usage."""
582 return bool(self.info.memory_usage)
583
584 @property
585 def memory_usage_string(self) -> str:
586 """Memory usage string with proper size qualifier."""
587 return self.info.memory_usage_string
588
589 @property
590 def non_null_counts(self) -> list[int] | Series:
591 return self.info.non_null_counts
592
593 def add_object_type_line(self) -> None:
594 """Add line with string representation of dataframe to the table."""
595 self._lines.append(str(type(self.data)))
596
597 def add_index_range_line(self) -> None:
598 """Add line with range of indices to the table."""
599 self._lines.append(self.data.index._summary())
600
601 def add_dtypes_line(self) -> None:
602 """Add summary line with dtypes present in dataframe."""
603 collected_dtypes = [
604 f"{key}({val:d})" for key, val in sorted(self.dtype_counts.items())
605 ]
606 self._lines.append(f"dtypes: {', '.join(collected_dtypes)}")
607
608
609class _DataFrameTableBuilder(_TableBuilderAbstract):
610 """
611 Abstract builder for dataframe info table.
612
613 Parameters
614 ----------
615 info : DataFrameInfo.
616 Instance of DataFrameInfo.
617 """
618
619 def __init__(self, *, info: DataFrameInfo) -> None:
620 self.info: DataFrameInfo = info
621
622 def get_lines(self) -> list[str]:
623 self._lines = []
624 if self.col_count == 0:
625 self._fill_empty_info()
626 else:
627 self._fill_non_empty_info()
628 return self._lines
629
630 def _fill_empty_info(self) -> None:
631 """Add lines to the info table, pertaining to empty dataframe."""
632 self.add_object_type_line()
633 self.add_index_range_line()
634 self._lines.append(f"Empty {type(self.data).__name__}\n")
635
636 @abstractmethod
637 def _fill_non_empty_info(self) -> None:
638 """Add lines to the info table, pertaining to non-empty dataframe."""
639
640 @property
641 def data(self) -> DataFrame:
642 """DataFrame."""
643 return self.info.data
644
645 @property
646 def ids(self) -> Index:
647 """Dataframe columns."""
648 return self.info.ids
649
650 @property
651 def col_count(self) -> int:
652 """Number of dataframe columns to be summarized."""
653 return self.info.col_count
654
655 def add_memory_usage_line(self) -> None:
656 """Add line containing memory usage."""
657 self._lines.append(f"memory usage: {self.memory_usage_string}")
658
659
660class _DataFrameTableBuilderNonVerbose(_DataFrameTableBuilder):
661 """
662 Dataframe info table builder for non-verbose output.
663 """
664
665 def _fill_non_empty_info(self) -> None:
666 """Add lines to the info table, pertaining to non-empty dataframe."""
667 self.add_object_type_line()
668 self.add_index_range_line()
669 self.add_columns_summary_line()
670 self.add_dtypes_line()
671 if self.display_memory_usage:
672 self.add_memory_usage_line()
673
674 def add_columns_summary_line(self) -> None:
675 self._lines.append(self.ids._summary(name="Columns"))
676
677
678class _TableBuilderVerboseMixin(_TableBuilderAbstract):
679 """
680 Mixin for verbose info output.
681 """
682
683 SPACING: str = " " * 2
684 strrows: Sequence[Sequence[str]]
685 gross_column_widths: Sequence[int]
686 with_counts: bool
687
688 @property
689 @abstractmethod
690 def headers(self) -> Sequence[str]:
691 """Headers names of the columns in verbose table."""
692
693 @property
694 def header_column_widths(self) -> Sequence[int]:
695 """Widths of header columns (only titles)."""
696 return [len(col) for col in self.headers]
697
698 def _get_gross_column_widths(self) -> Sequence[int]:
699 """Get widths of columns containing both headers and actual content."""
700 body_column_widths = self._get_body_column_widths()
701 return [
702 max(*widths)
703 for widths in zip(
704 self.header_column_widths, body_column_widths, strict=False
705 )
706 ]
707
708 def _get_body_column_widths(self) -> Sequence[int]:
709 """Get widths of table content columns."""
710 strcols: Sequence[Sequence[str]] = list(zip(*self.strrows, strict=True))
711 return [max(len(x) for x in col) for col in strcols]
712
713 def _gen_rows(self) -> Iterator[Sequence[str]]:
714 """
715 Generator function yielding rows content.
716
717 Each element represents a row comprising a sequence of strings.
718 """
719 if self.with_counts:
720 return self._gen_rows_with_counts()
721 else:
722 return self._gen_rows_without_counts()
723
724 @abstractmethod
725 def _gen_rows_with_counts(self) -> Iterator[Sequence[str]]:
726 """Iterator with string representation of body data with counts."""
727
728 @abstractmethod
729 def _gen_rows_without_counts(self) -> Iterator[Sequence[str]]:
730 """Iterator with string representation of body data without counts."""
731
732 def add_header_line(self) -> None:
733 header_line = self.SPACING.join(
734 [
735 _put_str(header, col_width)
736 for header, col_width in zip(
737 self.headers, self.gross_column_widths, strict=True
738 )
739 ]
740 )
741 self._lines.append(header_line)
742
743 def add_separator_line(self) -> None:
744 separator_line = self.SPACING.join(
745 [
746 _put_str("-" * header_colwidth, gross_colwidth)
747 for header_colwidth, gross_colwidth in zip(
748 self.header_column_widths, self.gross_column_widths, strict=True
749 )
750 ]
751 )
752 self._lines.append(separator_line)
753
754 def add_body_lines(self) -> None:
755 for row in self.strrows:
756 body_line = self.SPACING.join(
757 [
758 _put_str(col, gross_colwidth)
759 for col, gross_colwidth in zip(
760 row, self.gross_column_widths, strict=True
761 )
762 ]
763 )
764 self._lines.append(body_line)
765
766 def _gen_non_null_counts(self) -> Iterator[str]:
767 """Iterator with string representation of non-null counts."""
768 for count in self.non_null_counts:
769 yield f"{count} non-null"
770
771 def _gen_dtypes(self) -> Iterator[str]:
772 """Iterator with string representation of column dtypes."""
773 for dtype in self.dtypes:
774 yield pprint_thing(dtype)
775
776
777class _DataFrameTableBuilderVerbose(_DataFrameTableBuilder, _TableBuilderVerboseMixin):
778 """
779 Dataframe info table builder for verbose output.
780 """
781
782 def __init__(
783 self,
784 *,
785 info: DataFrameInfo,
786 with_counts: bool,
787 ) -> None:
788 self.info = info
789 self.with_counts = with_counts
790 self.strrows: Sequence[Sequence[str]] = list(self._gen_rows())
791 self.gross_column_widths: Sequence[int] = self._get_gross_column_widths()
792
793 def _fill_non_empty_info(self) -> None:
794 """Add lines to the info table, pertaining to non-empty dataframe."""
795 self.add_object_type_line()
796 self.add_index_range_line()
797 self.add_columns_summary_line()
798 self.add_header_line()
799 self.add_separator_line()
800 self.add_body_lines()
801 self.add_dtypes_line()
802 if self.display_memory_usage:
803 self.add_memory_usage_line()
804
805 @property
806 def headers(self) -> Sequence[str]:
807 """Headers names of the columns in verbose table."""
808 if self.with_counts:
809 return [" # ", "Column", "Non-Null Count", "Dtype"]
810 return [" # ", "Column", "Dtype"]
811
812 def add_columns_summary_line(self) -> None:
813 self._lines.append(f"Data columns (total {self.col_count} columns):")
814
815 def _gen_rows_without_counts(self) -> Iterator[Sequence[str]]:
816 """Iterator with string representation of body data without counts."""
817 yield from zip(
818 self._gen_line_numbers(),
819 self._gen_columns(),
820 self._gen_dtypes(),
821 strict=True,
822 )
823
824 def _gen_rows_with_counts(self) -> Iterator[Sequence[str]]:
825 """Iterator with string representation of body data with counts."""
826 yield from zip(
827 self._gen_line_numbers(),
828 self._gen_columns(),
829 self._gen_non_null_counts(),
830 self._gen_dtypes(),
831 strict=True,
832 )
833
834 def _gen_line_numbers(self) -> Iterator[str]:
835 """Iterator with string representation of column numbers."""
836 for i, _ in enumerate(self.ids):
837 yield f" {i}"
838
839 def _gen_columns(self) -> Iterator[str]:
840 """Iterator with string representation of column names."""
841 for col in self.ids:
842 yield pprint_thing(col)
843
844
845class _SeriesTableBuilder(_TableBuilderAbstract):
846 """
847 Abstract builder for series info table.
848
849 Parameters
850 ----------
851 info : SeriesInfo.
852 Instance of SeriesInfo.
853 """
854
855 def __init__(self, *, info: SeriesInfo) -> None:
856 self.info: SeriesInfo = info
857
858 def get_lines(self) -> list[str]:
859 self._lines = []
860 self._fill_non_empty_info()
861 return self._lines
862
863 @property
864 def data(self) -> Series:
865 """Series."""
866 return self.info.data
867
868 def add_memory_usage_line(self) -> None:
869 """Add line containing memory usage."""
870 self._lines.append(f"memory usage: {self.memory_usage_string}")
871
872 @abstractmethod
873 def _fill_non_empty_info(self) -> None:
874 """Add lines to the info table, pertaining to non-empty series."""
875
876
877class _SeriesTableBuilderNonVerbose(_SeriesTableBuilder):
878 """
879 Series info table builder for non-verbose output.
880 """
881
882 def _fill_non_empty_info(self) -> None:
883 """Add lines to the info table, pertaining to non-empty series."""
884 self.add_object_type_line()
885 self.add_index_range_line()
886 self.add_dtypes_line()
887 if self.display_memory_usage:
888 self.add_memory_usage_line()
889
890
891class _SeriesTableBuilderVerbose(_SeriesTableBuilder, _TableBuilderVerboseMixin):
892 """
893 Series info table builder for verbose output.
894 """
895
896 def __init__(
897 self,
898 *,
899 info: SeriesInfo,
900 with_counts: bool,
901 ) -> None:
902 self.info = info
903 self.with_counts = with_counts
904 self.strrows: Sequence[Sequence[str]] = list(self._gen_rows())
905 self.gross_column_widths: Sequence[int] = self._get_gross_column_widths()
906
907 def _fill_non_empty_info(self) -> None:
908 """Add lines to the info table, pertaining to non-empty series."""
909 self.add_object_type_line()
910 self.add_index_range_line()
911 self.add_series_name_line()
912 self.add_header_line()
913 self.add_separator_line()
914 self.add_body_lines()
915 self.add_dtypes_line()
916 if self.display_memory_usage:
917 self.add_memory_usage_line()
918
919 def add_series_name_line(self) -> None:
920 self._lines.append(f"Series name: {self.data.name}")
921
922 @property
923 def headers(self) -> Sequence[str]:
924 """Headers names of the columns in verbose table."""
925 if self.with_counts:
926 return ["Non-Null Count", "Dtype"]
927 return ["Dtype"]
928
929 def _gen_rows_without_counts(self) -> Iterator[Sequence[str]]:
930 """Iterator with string representation of body data without counts."""
931 yield from ([dtype] for dtype in self._gen_dtypes())
932
933 def _gen_rows_with_counts(self) -> Iterator[Sequence[str]]:
934 """Iterator with string representation of body data with counts."""
935 yield from zip(self._gen_non_null_counts(), self._gen_dtypes(), strict=True)
936
937
938def _get_dataframe_dtype_counts(df: DataFrame) -> Mapping[str, int]:
939 """
940 Create mapping between datatypes and their number of occurrences.
941 """
942 # groupby dtype.name to collect e.g. Categorical columns
943 return df.dtypes.value_counts().groupby(lambda x: x.name).sum()