Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/io/formats/string.py: 24%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

139 statements  

1""" 

2Module for formatting output data in console (to string). 

3""" 

4 

5from __future__ import annotations 

6 

7from shutil import get_terminal_size 

8from typing import TYPE_CHECKING 

9 

10import numpy as np 

11 

12from pandas.io.formats.printing import pprint_thing 

13 

14if TYPE_CHECKING: 

15 from collections.abc import Iterable 

16 

17 from pandas.io.formats.format import DataFrameFormatter 

18 

19 

20class StringFormatter: 

21 """Formatter for string representation of a dataframe.""" 

22 

23 def __init__(self, fmt: DataFrameFormatter, line_width: int | None = None) -> None: 

24 self.fmt = fmt 

25 self.adj = fmt.adj 

26 self.frame = fmt.frame 

27 self.line_width = line_width 

28 

29 def to_string(self) -> str: 

30 text = self._get_string_representation() 

31 if self.fmt.should_show_dimensions: 

32 text = f"{text}{self.fmt.dimensions_info}" 

33 return text 

34 

35 def _get_strcols(self) -> list[list[str]]: 

36 strcols = self.fmt.get_strcols() 

37 if self.fmt.is_truncated: 

38 strcols = self._insert_dot_separators(strcols) 

39 return strcols 

40 

41 def _get_string_representation(self) -> str: 

42 if self.fmt.frame.empty: 

43 return self._empty_info_line 

44 

45 strcols = self._get_strcols() 

46 

47 if self.line_width is None: 

48 # no need to wrap around just print the whole frame 

49 return self.adj.adjoin(1, *strcols) 

50 

51 if self._need_to_wrap_around: 

52 return self._join_multiline(strcols) 

53 

54 return self._fit_strcols_to_terminal_width(strcols) 

55 

56 @property 

57 def _empty_info_line(self) -> str: 

58 return ( 

59 f"Empty {type(self.frame).__name__}\n" 

60 f"Columns: {pprint_thing(self.frame.columns)}\n" 

61 f"Index: {pprint_thing(self.frame.index)}" 

62 ) 

63 

64 @property 

65 def _need_to_wrap_around(self) -> bool: 

66 return bool(self.fmt.max_cols is None or self.fmt.max_cols > 0) 

67 

68 def _insert_dot_separators(self, strcols: list[list[str]]) -> list[list[str]]: 

69 str_index = self.fmt._get_formatted_index(self.fmt.tr_frame) 

70 index_length = len(str_index) 

71 

72 if self.fmt.is_truncated_horizontally: 

73 strcols = self._insert_dot_separator_horizontal(strcols, index_length) 

74 

75 if self.fmt.is_truncated_vertically: 

76 strcols = self._insert_dot_separator_vertical(strcols, index_length) 

77 

78 return strcols 

79 

80 @property 

81 def _adjusted_tr_col_num(self) -> int: 

82 return self.fmt.tr_col_num + 1 if self.fmt.index else self.fmt.tr_col_num 

83 

84 def _insert_dot_separator_horizontal( 

85 self, strcols: list[list[str]], index_length: int 

86 ) -> list[list[str]]: 

87 strcols.insert(self._adjusted_tr_col_num, [" ..."] * index_length) 

88 return strcols 

89 

90 def _insert_dot_separator_vertical( 

91 self, strcols: list[list[str]], index_length: int 

92 ) -> list[list[str]]: 

93 n_header_rows = index_length - len(self.fmt.tr_frame) 

94 row_num = self.fmt.tr_row_num 

95 for ix, col in enumerate(strcols): 

96 cwidth = self.adj.len(col[row_num]) 

97 

98 if self.fmt.is_truncated_horizontally: 

99 is_dot_col = ix == self._adjusted_tr_col_num 

100 else: 

101 is_dot_col = False 

102 

103 if cwidth > 3 or is_dot_col: 

104 dots = "..." 

105 else: 

106 dots = ".." 

107 

108 if ix == 0 and self.fmt.index: 

109 dot_mode = "left" 

110 elif is_dot_col: 

111 cwidth = 4 

112 dot_mode = "right" 

113 else: 

114 dot_mode = "right" 

115 

116 dot_str = self.adj.justify([dots], cwidth, mode=dot_mode)[0] 

117 col.insert(row_num + n_header_rows, dot_str) 

118 return strcols 

119 

120 def _join_multiline(self, strcols_input: Iterable[list[str]]) -> str: 

121 lwidth = self.line_width 

122 adjoin_width = 1 

123 strcols = list(strcols_input) 

124 

125 if self.fmt.index: 

126 idx = strcols.pop(0) 

127 lwidth -= np.array([self.adj.len(x) for x in idx]).max() + adjoin_width 

128 

129 col_widths = [ 

130 np.array([self.adj.len(x) for x in col]).max() if len(col) > 0 else 0 

131 for col in strcols 

132 ] 

133 

134 assert lwidth is not None 

135 col_bins = _binify(col_widths, lwidth) 

136 nbins = len(col_bins) 

137 

138 str_lst = [] 

139 start = 0 

140 for i, end in enumerate(col_bins): 

141 row = strcols[start:end] 

142 if self.fmt.index: 

143 row.insert(0, idx) 

144 if nbins > 1: 

145 nrows = len(row[-1]) 

146 if end <= len(strcols) and i < nbins - 1: 

147 row.append([" \\"] + [" "] * (nrows - 1)) 

148 else: 

149 row.append([" "] * nrows) 

150 str_lst.append(self.adj.adjoin(adjoin_width, *row)) 

151 start = end 

152 return "\n\n".join(str_lst) 

153 

154 def _fit_strcols_to_terminal_width(self, strcols: list[list[str]]) -> str: 

155 from pandas import Series 

156 

157 lines = self.adj.adjoin(1, *strcols).split("\n") 

158 max_len = Series(lines).str.len().max() 

159 # plus truncate dot col 

160 width, _ = get_terminal_size() 

161 dif = max_len - width 

162 # '+ 1' to avoid too wide repr (GH PR #17023) 

163 adj_dif = dif + 1 

164 col_lens = Series([Series(ele).str.len().max() for ele in strcols]) 

165 n_cols = len(col_lens) 

166 counter = 0 

167 while adj_dif > 0 and n_cols > 1: 

168 counter += 1 

169 mid = round(n_cols / 2) 

170 mid_ix = col_lens.index[mid] 

171 col_len = col_lens[mid_ix] 

172 # adjoin adds one 

173 adj_dif -= col_len + 1 

174 col_lens = col_lens.drop(mid_ix) 

175 n_cols = len(col_lens) 

176 

177 # subtract index column 

178 max_cols_fitted = n_cols - self.fmt.index 

179 # GH-21180. Ensure that we print at least two. 

180 max_cols_fitted = max(max_cols_fitted, 2) 

181 self.fmt.max_cols_fitted = max_cols_fitted 

182 

183 # Call again _truncate to cut frame appropriately 

184 # and then generate string representation 

185 self.fmt.truncate() 

186 strcols = self._get_strcols() 

187 return self.adj.adjoin(1, *strcols) 

188 

189 

190def _binify(cols: list[int], line_width: int) -> list[int]: 

191 adjoin_width = 1 

192 bins = [] 

193 curr_width = 0 

194 i_last_column = len(cols) - 1 

195 for i, w in enumerate(cols): 

196 w_adjoined = w + adjoin_width 

197 curr_width += w_adjoined 

198 if i_last_column == i: 

199 wrap = curr_width + 1 > line_width and i > 0 

200 else: 

201 wrap = curr_width + 2 > line_width and i > 0 

202 if wrap: 

203 bins.append(i) 

204 curr_width = w_adjoined 

205 

206 bins.append(len(cols)) 

207 return bins