From a4f2d22aef15a03c6fbde496e4a0e042897e3f0c Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Sun, 24 Nov 2019 14:49:00 +0100 Subject: [PATCH 01/25] initial xlsb support --- pandas/core/config_init.py | 8 +++++ pandas/io/excel/_base.py | 12 ++++++-- pandas/io/excel/_pyxlsb.py | 63 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 pandas/io/excel/_pyxlsb.py diff --git a/pandas/core/config_init.py b/pandas/core/config_init.py index ba0a4d81a88d3..0b13f3c3794c3 100644 --- a/pandas/core/config_init.py +++ b/pandas/core/config_init.py @@ -478,6 +478,7 @@ def use_inf_as_na_cb(key): _xlsm_options = ["xlrd", "openpyxl"] _xlsx_options = ["xlrd", "openpyxl"] _ods_options = ["odf"] +_xlsb_options = ["pyxlsb"] with cf.config_prefix("io.excel.xls"): @@ -514,6 +515,13 @@ def use_inf_as_na_cb(key): validator=str, ) +with cf.config_prefix("io.excel.xlsb"): + cf.register_option( + "reader", + "auto", + reader_engine_doc.format(ext="xlsb", others=", ".join(_ods_options)), + validator=str, + ) # Set up the io.excel specific writer configuration. writer_engine_doc = """ diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index c442f0d9bf66c..46cb67e4512e0 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -794,15 +794,21 @@ class ExcelFile: If a string or path object, expected to be a path to xls, xlsx or odf file. engine : str, default None If io is not a buffer or path, this must be set to identify io. - Acceptable values are None, ``xlrd``, ``openpyxl`` or ``odf``. + Acceptable values are None, ``xlrd``, ``openpyxl``, ``odf``, or ``pyxlsb``. Note that ``odf`` reads tables out of OpenDocument formatted files. """ from pandas.io.excel._odfreader import _ODFReader from pandas.io.excel._openpyxl import _OpenpyxlReader from pandas.io.excel._xlrd import _XlrdReader - - _engines = {"xlrd": _XlrdReader, "openpyxl": _OpenpyxlReader, "odf": _ODFReader} + from pandas.io.excel._pyxlsb import _PyxlsbReader + + _engines = { + "xlrd": _XlrdReader, + "openpyxl": _OpenpyxlReader, + "odf": _ODFReader, + "pyxlsb": _PyxlsbReader, + } def __init__(self, io, engine=None): if engine is None: diff --git a/pandas/io/excel/_pyxlsb.py b/pandas/io/excel/_pyxlsb.py new file mode 100644 index 0000000000000..f781c02f7814e --- /dev/null +++ b/pandas/io/excel/_pyxlsb.py @@ -0,0 +1,63 @@ +from pandas._typing import FilePathOrBuffer, Scalar +from pandas.compat._optional import import_optional_dependency +from pandas.io.excel._base import _BaseExcelReader + +from typing import List + + +class _PyxlsbReader(_BaseExcelReader): + def __init__(self, filepath_or_buffer: FilePathOrBuffer) -> None: + """Reader using pyxlsb engine. + + Parameters + __________ + filepath_or_buffer: string, path object, or Workbook + Object to be parsed. + """ + import_optional_dependency("pyxlsb") + # This will call load_workbook on the filepath or buffer + # And set the result to the book-attribute + super().__init__(filepath_or_buffer) + + @property + def _workbook_class(self): + from pyxlsb import Workbook + + return Workbook + + def load_workbook(self, filepath_or_buffer: FilePathOrBuffer): + from pyxlsb import open_workbook + + # Todo: hack in buffer capability + # This might need some modifications to the Pyxlsb library + # Actual work for opening it is in xlsbpackage.py, line 20-ish + + return open_workbook(filepath_or_buffer) + + @property + def sheet_names(self) -> List[str]: + return self.book.sheets + + def get_sheet_by_name(self, name: str): + return self.book.get_sheet(name) + + def get_sheet_by_index(self, index: int): + # pyxlsb sheets are indexed from 1 onwards + # There's a fix for this in the source, but the pypi package doesn't have it + return self.book.get_sheet(index + 1) + + def _convert_cell(self, cell, convert_float: bool) -> Scalar: + # Todo: there is no way to distinguish between floats and datetimes in pyxlsb + # This means that there is no way to read datetime types from an xlsb file yet + + if isinstance(cell.v, float) and convert_float: + val = int(cell.v) + if val == cell.v: + return val + else: + return float(cell.v) + + return cell.v + + def get_sheet_data(self, sheet, convert_float: bool) -> List[List[Scalar]]: + return [[self._convert_cell(c, convert_float) for c in r] for r in sheet] From 62564cf1e6d5fb0cc591ba1e31829a9f399ec2c4 Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Mon, 25 Nov 2019 19:58:23 +0100 Subject: [PATCH 02/25] Import order fix for CI pass --- pandas/io/excel/_pyxlsb.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pandas/io/excel/_pyxlsb.py b/pandas/io/excel/_pyxlsb.py index f781c02f7814e..667ac23426a55 100644 --- a/pandas/io/excel/_pyxlsb.py +++ b/pandas/io/excel/_pyxlsb.py @@ -1,8 +1,10 @@ -from pandas._typing import FilePathOrBuffer, Scalar +from typing import List + from pandas.compat._optional import import_optional_dependency -from pandas.io.excel._base import _BaseExcelReader -from typing import List +from pandas._typing import FilePathOrBuffer, Scalar + +from pandas.io.excel._base import _BaseExcelReader class _PyxlsbReader(_BaseExcelReader): From a7a8460691ad3cc05ab59e5a2433bc705e65ad6e Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Tue, 26 Nov 2019 20:18:50 +0100 Subject: [PATCH 03/25] Initial tests --- pandas/tests/io/data/excel/blank.xlsb | Bin 0 -> 8908 bytes .../io/data/excel/blank_with_header.xlsb | Bin 0 -> 9129 bytes pandas/tests/io/data/excel/test1.xlsb | Bin 0 -> 11359 bytes pandas/tests/io/data/excel/test2.xlsb | Bin 0 -> 7579 bytes pandas/tests/io/data/excel/test3.xlsb | Bin 0 -> 7553 bytes pandas/tests/io/data/excel/test4.xlsb | Bin 0 -> 7646 bytes pandas/tests/io/data/excel/test5.xlsb | Bin 0 -> 7824 bytes .../tests/io/data/excel/test_converters.xlsb | Bin 0 -> 7810 bytes .../io/data/excel/test_index_name_pre17.xlsb | Bin 0 -> 11097 bytes .../tests/io/data/excel/test_multisheet.xlsb | Bin 0 -> 10707 bytes pandas/tests/io/data/excel/test_squeeze.xlsb | Bin 0 -> 8567 bytes pandas/tests/io/data/excel/test_types.xlsb | Bin 0 -> 8053 bytes .../tests/io/data/excel/testdateoverflow.xlsb | Bin 0 -> 9856 bytes pandas/tests/io/data/excel/testdtype.xlsb | Bin 0 -> 7697 bytes .../tests/io/data/excel/testmultiindex.xlsb | Bin 0 -> 18853 bytes pandas/tests/io/data/excel/testskiprows.xlsb | Bin 0 -> 7699 bytes pandas/tests/io/data/excel/times_1900.xlsb | Bin 0 -> 7773 bytes pandas/tests/io/data/excel/times_1904.xlsb | Bin 0 -> 7734 bytes pandas/tests/io/excel/conftest.py | 2 +- pandas/tests/io/excel/test_readers.py | 18 ++++++++++++++++-- 20 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 pandas/tests/io/data/excel/blank.xlsb create mode 100644 pandas/tests/io/data/excel/blank_with_header.xlsb create mode 100644 pandas/tests/io/data/excel/test1.xlsb create mode 100644 pandas/tests/io/data/excel/test2.xlsb create mode 100644 pandas/tests/io/data/excel/test3.xlsb create mode 100644 pandas/tests/io/data/excel/test4.xlsb create mode 100644 pandas/tests/io/data/excel/test5.xlsb create mode 100644 pandas/tests/io/data/excel/test_converters.xlsb create mode 100644 pandas/tests/io/data/excel/test_index_name_pre17.xlsb create mode 100644 pandas/tests/io/data/excel/test_multisheet.xlsb create mode 100644 pandas/tests/io/data/excel/test_squeeze.xlsb create mode 100644 pandas/tests/io/data/excel/test_types.xlsb create mode 100644 pandas/tests/io/data/excel/testdateoverflow.xlsb create mode 100644 pandas/tests/io/data/excel/testdtype.xlsb create mode 100644 pandas/tests/io/data/excel/testmultiindex.xlsb create mode 100644 pandas/tests/io/data/excel/testskiprows.xlsb create mode 100644 pandas/tests/io/data/excel/times_1900.xlsb create mode 100644 pandas/tests/io/data/excel/times_1904.xlsb diff --git a/pandas/tests/io/data/excel/blank.xlsb b/pandas/tests/io/data/excel/blank.xlsb new file mode 100644 index 0000000000000000000000000000000000000000..d72fd68ab3dbf214b3b0c4b5ace56b34ce627b13 GIT binary patch literal 8908 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 NVF3UX-zHzc{{Z37J9_{C literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/excel/blank_with_header.xlsb b/pandas/tests/io/data/excel/blank_with_header.xlsb new file mode 100644 index 0000000000000000000000000000000000000000..3c241513d221abd0276082638a7e20f1a0c00a6a GIT binary patch literal 9129 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 J^gVF`{trvud>{Y- literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/excel/test1.xlsb b/pandas/tests/io/data/excel/test1.xlsb new file mode 100644 index 0000000000000000000000000000000000000000..d0b8a1f2735bd4063fb4853577f1ecb5968b473f GIT binary patch literal 11359 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

ghjW4ZOG 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 XTB=yzLp=e206>psuN`_Y0s#CEceHL? literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/excel/test3.xlsb b/pandas/tests/io/data/excel/test3.xlsb new file mode 100644 index 0000000000000000000000000000000000000000..617d27630e8a06a5eca901b92f91e896fbb4ca43 GIT binary patch literal 7553 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 literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/excel/test5.xlsb b/pandas/tests/io/data/excel/test5.xlsb new file mode 100644 index 0000000000000000000000000000000000000000..022ebef25aee2403bdd974bd7d02036e4ba49906 GIT binary patch literal 7824 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 W!pE8(06>cUFk`LvIyTAx0RI7F?$F=> literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/excel/test_converters.xlsb b/pandas/tests/io/data/excel/test_converters.xlsb new file mode 100644 index 0000000000000000000000000000000000000000..c39c33d8dd94f0a1928bd1658333625825d32d77 GIT binary patch literal 7810 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 Ef8hk^1^@s6 literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/excel/test_index_name_pre17.xlsb b/pandas/tests/io/data/excel/test_index_name_pre17.xlsb new file mode 100644 index 0000000000000000000000000000000000000000..5251b8f3b31941aeeedd0a45df83d2018d75afe0 GIT binary patch literal 11097 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 IxC8+3e@VK02LJ#7 literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/excel/test_squeeze.xlsb b/pandas/tests/io/data/excel/test_squeeze.xlsb new file mode 100644 index 0000000000000000000000000000000000000000..6aadd727e957b4b0aee69ebf8609dc926ab8f3d0 GIT binary patch literal 8567 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 literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/excel/test_types.xlsb b/pandas/tests/io/data/excel/test_types.xlsb new file mode 100644 index 0000000000000000000000000000000000000000..e7403aa288263456f3468a721206ad1ab786b586 GIT binary patch literal 8053 zcmeHMgFa2mq)+ z{rgUiZcs-zV@+>osH-8DmxDb`9uklx2LObB|KIC>*aOL$11cRnxJ~qjIEhs(9s>xp zlXWO8_{7CBcvjO({A!f*M~#eEz5?6#RqIP!BA?Ch)$%P3S*zM3q9dNG5`CTv{gvos@p zb6CNFdc_)k=atC;O>!-sy^?tctUI5$cYUtYLT_TzkTH6^X3g8PGO<7HWGPwUA)BUs z`No|>RdyZ<4W{CnHY#+wi8>w1* zaEZ>WinDxEwUx8v?eu(<2vH+HTx~C{56v?Si*3ekc;Za~Q#PFjdBB<9HOJT;owT)<_k-h@k1jkMsBDD#)i()_ekcDjH~Mad(SvF7N6=%o8|2 zZ*C9)s=qMze$Z;yIXoPd;Kam$GuPM!YVXR;_5JxTWB-R~`j@H4B#posi`l&|*K~jF z;*+BsYHs~InwN{XY&gq|j^y>R`3#chXBN!G{0)>>`^O{MO^ZVl#VtZI?3F?8YC&d2 zUAog+2V+f@FC@EUhDTZA*o9m|4E>+H_TNlYr;2JmRfFCkX5Z2{8i`Ol7gOWttQ~l! z-7do#o_inSlN0wJjg)wjYg@_YQB7YBshJEE)uFM?q3QYphl&m2v222OZkC{<%o$Pv zc4}UIIRWE*GIYu!^q8&JnQ?MU#H8d0Vq+T~c{f#Ekp!d)A$maL4Pkc!pxy^;x5YeY zPnGC##0UJa;9gaVa?$6((bUCH;8XuQPd=RnYo%Gj$gg46d0wrd(W39|= zLGI%LirgtLI9tz;R!%qa_d5~j>tXCHH55ob1ok`RpsJ~fAw*QOy^7C4vLJeV`~ID> zQ$COM3H-MnG>N)mtj>cS`N3;J*1tB#xMH)62z3p! zMTq)h#@mhK-Wk0%R=YYj>}A;HZpoUAQZV`4p|c zgy>QKwjfSd5>*`S=RVq_z8*;Q8b9kb`kJFW-kaLK?V zJcAZ%Y5+w^x6&FgFV8U_zY_5%0Vv7~tPAesVF14b4)t2~n)f2DxP_U8@#e_kC8${T z0;1LdQ^nvqKHfp|}Crdw6w5LwL}m}F|7yC zkM#suu zqc}2qc-+HWQF7*x1}2d*rq$^oS%!tNi(SDO_|Woh%3SD#;vMe_$O{)uwe9C&zzk*1 z9WL&agFb_%>B6GKA=!#{=_Rk#--{T7wU!@_M%4BA686Uhkz4@tx#GsrHHdsYfja5u)-|%Et=BDsT_!qG{2ab zR|oacTj$;ZLXIc*{ZB?uMj;K`z%x#42Zh;1Q`ZL#IThPgM^F0`y8;UohPufA(e1fv zOldIq2nF2r^uM|Nd%U??L!oZ2zo)DpAHaVmEc+3dEu5&x%NIHOCT_-=Cp_H|8Z)K| zcH2~vwa+^FQ(~bCM`x!FdozcrUnFv`>q4~_S)$|ZbL{j~I|W2WY6JHNrgubHSbMRj zdo4_u11FrlwnysBN*Evc;cML?LR4hkzHj#m1C-3fw9@$83|rTBuvW#oO6aM0!(PU4 zJTfMvkSr@utPy(>3OPNZ7WK-=?=0aLK&C9}o4?U)uE>4?-lrFxD-HE7G+W;EoVj~9 zfXe6UWWpKcp8^j?jN1|jAL*v~-i!QToSQY&0m}X33I5)T>^>TbCYL2@g7fE^qka6PKD+`YWC=lkBsa>IAH}NG=1k9OQB|OsSV($a=V|ML3<_^M_)Jn952bijR-Fr{9|{gNWpJ!!R9Su} zyb4Y_=qFw$mEFt{EL_(s7-#8;U}#CrA(<3U8|fqTB`6qgtydF&d5|ubi(Hr-|IF52 z-g#hHQS+_MEYyQ#-)3cW+ExXGjEG{K*0eHFRzIyWY>1p9W*v1&qsvf0lAp zt=YJ7WE8CK_a+#}cW?)6hk!f(`(CmVT<9dT!JO+V71y5R&G^XEN{0G)(wut3p37#5K?LcNd(p;hQl?V z$W3c>wFTMaOy?EK#0N-$uVv5(cnnIk9zGMf)XA2Vu!+w@jKQ?&!bpqAqV4J-&`?RK zS^WU2XhHD4_;#9+D$(ry^*BSPYN_c`N8q(Kw2v~+!nFVDaPh;<)e!O0zQp5;p41z; zqNQ#Of}79TPyEjZs`fQ-*twJr&&sHmu1-4Ut8UKb&y7XuJcUKT9|`cyKQ8ucF3}Tp z04XmI(ONS*8KgUtB=NR9imCd0flJ2ppW08W9om^GYGN?FtWGCaMB=_E;P(uk=gk1A z_#XPkh+;WIhM>A$H3~$Z`@Z&f-jm)Myz5ELm=YXb#`scaMSV8D`oS^ngGd>SOq1=B z{TbHoxG8^?2f}qSW|b3VWprspyD=KVM-F}1 z56ji?>02Pj{s)aV$F!QlR&4%~MMX>p;(=#4D_u6pC_pz7ShLPf2kN7G#byl8rpmYL ztD)Z&_&!)b@`X=3X4MPMisOboAJGEkso$yZ32Z87l}c*gBtmyhfQ%N6=%$0c$2L zX1Xs<8F=2kzVX+ob>cQY=e-b#+cD}6r&?`H?NiYfSK-#E;~t$Yo`^e1md*a!=^O92 z5i3h=I zHIAq(vjYh{Ottcplnm(^Qcwo}LX3y538U;6EF5##M4SbWS4%00BstH~FW1hN!+m;i z5OEP`g<^uw{0c9V8+}#{_Xgv)xBa{8=m2`V%1DGS9N8;$;RrypU9m|Xke9jUn+M@UP zGjjSCMc!L2y9C_py0y6k?Q8qBw(W+ehkkKI<8JT3c^p|S(qRzS&|`hh$L`fh7VOUF zLg`t)%x+!Ow5YGDA88?H;vo>DOiiZq0R>IH>DX7C%6OnQ(@jz1;N&>^#u!y?wLyy) z?e1(N+gq;}kh}F8EP0Aelge>laA*eJjkVR9XtI4tai+b9gTXpLDqmX+cfFd{L{?Wh zsQ}buAVr7$2)QgalD6pi9dYHwdDhp>eeU}H(ngGJ%WtnJw9bJbSdO-mrJ6>J=R|e? z>};{2r~t+#pgx)qjebW^!1ncS$~5Yx8;@5(KaR`18pi!&W#bagimJgOeFu*t!lWKR z`TXV+#k&axrD<=bUX#>C+WA@csDjrJ`)D>stqSy@Cx_ z4@v%3v;3@9{E}L~OGn-NnYcxhnzOhVW&v@iB^U&eGmpHGH4jNZXbdJFjX{Wr@#D`( zBTM8kLGBhNC{I)>J#3Pnu0TA+KkN@5qD@d%wRA?IyPWBhX8I1(DC(9=jihmT`BS`= ztS8it4{D=#GLvYa)-h%Ai-fgWh*-@jq-lmDcW5$B7L4?s%*n9sHRcP7o2oL6>S@&s z;sSgo9ya96^-^h)YdJ{QCnDs`<#3W{l25ji7S{>pE2>Rr3-xLuT#4%NiHt$0UWW`b zk~x}5#J{f$$q~e6>H_N7^5>PHj+Gv4+1hH#Qz{iUwA^FZ%ERWIo{CVBr5d8X0#GU~ zrf2wgJK8or*2~muw3o4hO)B-AT3=KKVf5@DX~bjOaZ%_1?PTUXllnwSTHK5F9G1$O zPwwdV-R#Gmid}!TRbSw}*Lku0Yy=XOlKAcP3HIf=9F#xK8=Fi%J#syPXaq?_j{AC+SA(G z!pU6K#mU)~+uX?o`nv}GZ)*-;y^thhNIMT<*9YZGY{l%wM(nh4d-`iMZUr+pC% zqa4$n^agZ4-**TEd6DtWnv2|T3F7z%>zM3f*AvXW!#;x{B+f^diaJ*P22 zu(0pKN4~29RPg)vzW?_=4Bi5px_HYwT0p&i^kL&kCIBFaCItvi2nY+jmmo;Z*4`Ts z&?xz8O0vAWx2+mC0-8obQiyn{$@E|PZBuzP+(>xv;~@e7xPJ|PQ)lOYV;{Z(zh9Y2 zQcm+cn4$ZKXVjRBGSQw zsK{I(PPS)HXPchCe4nUYgi}eYXa;?ESX6j0sA|nXQ|UG6B88IDy=1LPOW#hcTv%7I zo1Vp+s#un%V_cvcI$2tzg_B~&HF~vDZy3E&Bt_!PHkPrgMAJ=Xufg|ToECb8f6U$h z2vMjpC5u~M5UpBdZl=I8Fdz#LKnn>~?98Ia`I0}IrgTGNG#n`#7U|hOi|se@+_mMf z-*23Hc4vl>AdaI+=YxS)S5aN`dDwE@@IdZd1JnH>Zs+k4#M9o4!N;hNUNi0HihExZ zh4nmn@%W9=5?6ml(g$||jibxdQz!UB{KgjqASZk?`R6mlKbGr{_dlE{sw(~p@as{= zABNxGjc`u<6)ircoGw{_g*$0Nn<>Eus7bjKTZ?cw11p4RE`n`w1WpuO5EQg+J@N+bFk- zj-MzW@BHSH+a<_tgkLLypY{Mi7!d&QxBB3=`RyeB69Av|_B3v%^xIg!=I5VC06;Jt c>(9UPd$v|p1pUbKm;g)wB|LhiDZX3$AOEjL!TTTU$X=(NLLR0#K39|9kxpTcB30(PokhRENJR zN?wf@8-+*iPp5#djk)`p#4O;2T2N3}R}|#+i=BhC7(PuzOPK3wt3O-a!69>$o(`z) z6%CyMnH$5h-IZ%wSH}AaskV>zZ-13im6_1~=o27q1!!&kwB9`LNQ}ZN?P?_{2Yz$S zFVjyi4MZrknRn~M^=q^UxU5!r8>hHzcr#@9mp=4T87z|uRV67G@UBZ~QL&)hIc*^H?&v{V!w7usJE5IC zcy%GDIZkYHkf<)&b+;XLq4Yr(LF9_5&zjSLPy=^$xI9{rz<5}Z#8`#O;+&u^yigNz z&*{>>SNS#1=p|94v!0#;0g69FyoAp}>j82`9vRS=kP-jD&e+PHljGa(zmfhw9Nyo( zdT3O?LNgbrZBwRJV)6KcjSN;wSq&upJ;62nStc8*vamEpv4efs)okAK%U?Hl`jczl z_YP&(^GmW8_&iebG0bY!98=pGs4YkmYnAMK%N)+iZ|AG)?pfi!5~)lRT(_rWOhU%G zs`9BnQ0YKOiLIri`>A@9BuhYw#J$Ha!yl@|J|RD`5UYEu>Z&3>o`|W~JFwC_R+eF% zzf3leOkCq&g8u3GI0YXoEw{D|--9$toXc4_p{o_o!)2z)D5$oC29_ODPYYXvh$&=! z@1s6gzWGQ8_5KW=!~0a@_Cmp(z!@TcM28}Eo?tv8gf`prmvOSto+*Zjtc=4z0DuyJ zh2m_*`Fp51+1gne+S*!j7@FJsCu~rV1rqt}|MpcI-EGm#MId$HcY=s>XP-2ez>X|o z@w1xT1**Dyfs`}y4h5Xn=aSvQZG0bRg~o%}5ax0!ui308PA1QOq%Izt4tYzdQC_aT z<>*}RiYA_Cog-+f)aZp)Grwt$-;*rcHof| zX?3_oB=yx-i*;oX&gMl8|IQWt*R0Jjd#>M@46+AMx;5~iehzvg>B5*hMa&?4UC>t+ zF5Xf5{R6oPdm$#FnkO05Gi*T>dx@$yt6JwVf0b@2zzeS?k$!O(=^psVf213I??4tN zFf)B#LouMdmR7JrR{Y zm7H95mzYL)!mls09Qi!jDZ%r)9P?0@Z;~FybTDqA^bl>fHj=U@;OO zc`;_*CPgzem5$KVx8xEj&}KAg|Ef^s*M$^~T{n&TgQ8#+7eGzibN^LBOWZCc(1J;D)#) zpDqF6fSLvCwCFC$}GsjaQpmlCrFZ`!!E(b0oSbREN=Lb#CyO-Lc1l^!l?MfowA?6+f%1I>( z(OX7*a&y$H_|!_AZQZ$#-#Kd?&V;6>IDeI*fubPc#ttjP%$*Cn9xLoQG@99%dC4q ze7D5zU_~HSVW`30Fme7ceEQaww|zZagv|8Vb39@o02QiQS06A6HK+&x6~uo{jbIGL zEGKJ28IXrt;|M16LrPIHyvm{8ENta;rR-t@7hqLN${N`{N zvo*7VzEzWJNRLb4a3*;CbPuHsHi3~CAAx2*Gd<ok(e|T9kaJanskQA~{ZYS3@Sl(L!lLb%EkY<|_{CiI}HXd;h?T3O-Us z--x3P`3Hh6&wE_lSKJP!ZrWSTDYRfh$k~k|9ML7<1g+qv7`1fDC@}(Tb*mdKhZvU2 za$Adf@Fi$Cn_7nxtRw%b1J$KrsF-koL>@zk?z%ql9p$BnOee8K)6(0FOVW41Yh*zt zW`*d_=t(J_2O6mGNT(H;~`qIZiOScx}qnBEmZXgZ*^O7J6KP

O1s(`2|7~Vev~_2Ol3ACU93I4 z;4D1-!o<<^I*-B!DvKvFGJ!sokcSIiUb6Wu8DA2_2*Ej?-cBZtV}kFl-vXhdv#?Btk6Fo4^mo5WJaf>HEB!hIo~rBplQ3$`><9 z8fJVb8%gb|^YxvDd@DWOMk*FU6*;p@4#1u0!3?x$gnuN9&qIz<5wr3_tCcUfUOC(a zGn)rTd(V?m^k*>T@;RiEdK(|tC2q5Mt+#V;>gB6hRv)6Cjzsl+w5gc1$g0-N#DIzN z$sO)Z+_!%f{!M4#>%%f44|qo1sKYh}n46L|=B>a>gm{;5fc(wL z9N$8qTU~4u8FPofP)%td%af7UIw4!`92kJNV*VsykqK8}!R{UNG~sPNhWJeU%S5CW zVG}RQaXj~IhukbIww)2Ps4sLs8Jpj+^hxJ5OBI+7Xc%D?(-S^D8QIBN9of0sj-Py4 z^vl}eK-xlp=zTrBIlOQL{am-L=<&{PPX=i3y>a<`7-CV&Ht+d$F8YDTNi8MTURlv; z>$7_vuKSCv8p$Uo?-y_mV;dIe-6R?42iWa=_g&D+DcUO&qKo-yTbWyiQyS>D3iRJ= zqyTHLx98!EF%|4=;Fz(iHT+g`9A40@*aOawaAOFip5<7{>z2v@+L&pf<3BNZsK~C< z6I|&YivJ3B0AKel7nOUNb*n4Zedbb7+A>XZaT7{V(5<7(qL6f-b()Ou2TnPhm5VBV z8%o2irGrci0**xH;-wM{f!(#T;#g%1iSKK&yS1K;088)yjfUEm?uL4xhd=+Uvw7vF^1Scyt~5LVBV0BF zZNSzfl|FV1+v$@Nzp0A6{4;O=&E4(5HqWtP3cmu6)SQax>yK(!Us!$6E<#a%I@%Yc zUJp8;wVIuDCfy{=j<+LrAA2*iXSpRY?MWEG_lLQ137e`{jwOnBtTK z9y^tUD&3}iUW$g)Odd@2DBAfb0+A207$|rg1mkZZjU%^J{hA4`a?Otk_cBJX{3e#_ zD;M{r*N(|HEWjS|`szrmNb`6fUGc;caZPRRvywE~zBV(GxE=TvGc_LmS4AVNEGk7G zANh`yDVxTs-^h*6T$Sm*JIcXl`WWtFL&>&0?OTP}Sc;~nhhPP=Qz}TrMF#EHgej4d z68nXKB0<}}>Ci#PXGW?w#JlV=V1eb$R}dmjN%LhsrR65a7=wL8>8X{=L=Uxgrx|6n zFgR1urW9fsLPAI!J_1wEHQzh~IUb48r%$wTm*yDgWw10DvUh%%G4s~u?D7h>ao}TDAETo!;-nk*%4z%dD<^OaEz^^ zn5W=?=2odiO1>t$=B$$)Awed4>jOR-2oG6+${Y5$YW>rFj;(hdL`Zy{8GXKF&_&r7 zZ;^~4qd5LVl^N?Dq8#>m6Kt~Qu=$!ztE&U1Dy|!^S=PQ7Q53o1ZTZ;tKoKj)ruxnV z2519zQq^yc9tIR%1pn5%2Kzj1X*rvma@BEX|CtSPDhx!+71wW zHH4a6FUbWM_~pNR5&P9L7v7gY>X;YR8m68}Em>>@Wm^U+p^nMJYw{ph-LAQrykQ%l z`VhD&VJo%uKEMRS9H`~qiAN)IL#Bq`zp;~J0ydbJXE`)*$CZ6DkJEXucPR;8KDy4f zEs|BTGuK11GCVIOAh=hwU|CZ>JVNj;nh8g4XdPmNYE*B`>Ot7nen9tja_ebxD2sU% zcKtFDxyfou4v^V{bs)m#1pQaFt>DFF@qg4M^88ho=s+TwzUG6OqM=2rFP~{$6ptMb z`8b)FSm6A{^ze;=|4tI$j+9hd5kh4QLAOst&xtq9S?F|6zmy(x%D1yo9A{;F-^4i$ z*OGOOvLqq1NI=uFw|_ME0ZWQwRM4bJY*bOXCTZe;=lrex`b}Ev&(;mj#tAI^%qsle zRn%3wCq`MTA=y1;J5hN(_PeJ!jx&1nMmMkEmEO42eQTt#y?-mk5@nr#{8e(B?Jui4 zUV+(&H*$s)*>wHIj^pgzf4$>i^Or2;=62xj>NSuiLj6r%oG7j_+lL-M#!xcKA6i3B zF+om|#J%HXwWv>k@+!WggDWV~Kp=!zl0*iSs24}UMX8G$==m)Ej@KMsi8v*>P)Wc5 zf$(|X|CjCkKT!LuvHyRjc0J;`*|%Q&GIG!$#~*aSAKPGLLvCQ_a>oW{?0lyDV3a-p z6`jBvBa<77eE1i=8GnHKq9StT43X_JA(Ewn*%~R@*~0BPjco0V|2Vz*mz+YH&Miv( z9)gS9fAh)-=3t2;j%+!GW~vF9t|2&Wd$e?hkDK|fw(;=+>3D?HLb3adi2G5-n&AVD zX)-evK5hv{tVqnL8>umgpgK}HPk$k!cyOKtBWx9b!TzZ`B!;z%3in>M(o1XXHhV^M zW(|so`>&YW{B2WAuE25EnjWQDn!H!GEi!a#=krSAl858QVw+HGc-2z|tpd)wXC?T{4>GAktS2$7r!4tBeP%SC zaDG+!s_a-Y@AXL)-gTX8a#g38G#7l&v3gCdXZ$JEZZ+CN*A7}rYhe_PY5OX0khoAr*ifCyFFJl*d&w-R7VAw$7v#TyG2b6Y21L`AbnB>YSO$b z7ADWr3JlWVU}LhUT>)XK6c6LHuRZaowi_)zil|aF z-)C{TqGrTiO>p3;xG;G|+ltBBCb-dHElZ*}Tg{7mdPp3piTjjOk#2`|{uE9ZV&&Ca z;mBvZ_=|^jG52A}2c0Kiq`Vj&YV9LbV67~82BJ2-r! zOD^7@5tZxFy0Xn;1nsBhlHg1GSo63sIxqQx(qVVU8F>qfFL&Rn#x}pUsw5QkM8p;E z!EaVp613>JtdDvjU8yH+;pItE3OOi0w$yRTPF z9pgV8@_5}pO?C2A)DAV^hUokq`oSC)_m+F|UuKihs;5hD$TmMomr0dZ5R&11*h~qaFBkK>x;h>u`iyJ-j_Med^{W)d)<4Sne zef;-II56rK#D-ir3FP?O%6hhLzvDq-QK1oD{y3r$75LYhIso2wVjj!{2Y_t@NK#kJ zw~+l^ZxmE^B!l+rZzjGy_5JmWFDMk{egydO2=;s7PssfF-GS^y!5`10z89oK9zGyl z_AgGTF5+A~lsHE+!2Yu*`Og{GMd6D~{<*L@&Y!}6$Ln7NyvTW<14dlB2zbtbUqrdc z_nxCnAQ|Ex`>fy1bcS`#{$2#UNS~eqP9bX`67VN7^&-MWF7q5A67P3}Kbg&oq8B;0 zb5TZuGtr9-+(m$k8=G?g0NLM|<9wTQ5%6NeeGZ6<+&27Z-!sdeLH%=-s$Zj=tJD=~xZ&QCK{zo5lQTk%lIhV(# zJ$sRVQiCpn{^9J?lMZuf5(?M@JoqMGn9Q-~s>uX24Xavuz*-0APy+ z0FVK2F-;X+oZX?$?v@7L2&kJmzt>8OeDO@Q$(+}6X#Xcq1JTeu1 z`AYzsRmy0AIc&}GEM2w&E3fJ{tsS$ishlXy>`e5MDN!0R(x&qv_17fgqDw_rTtBsp zXl*F4u{MSkZ0VIQkhXud+5#t6k~nGDwzYKTQFSjO+ab3{Kbf;djZ`dndzK~ir0vZl ztKHx>a4K1%c&5W6NRJ=TjR$T^y`={dt9G>xh*hy6vHJp~ZeZD(Rj%`F#+#xHR9@MR zb+XWbd+o};uuC{xcBxvi>ZO^wpuMW*M4~uCeta;!~-=b+f0rF^RG`W0F zfTp!N+huJia*P;^{1orWG2$g2`NyJnv7A(OS&_Myx|_;rkWjQKaFU~pA-4@kc{SiH zIuMr2v}-xXhT$rPKfrofIpBz&+~I2K<6jsDhADW9HPxp15HePG;=Lop-yfB{NK+1G4_9$rvF-cbmAbIu>_r) zDh-MYC#%jXxH;AJ;QVP~ZlXChXS(W`yeo2t2M~@Tks5}to4bQg8>ah5i{6SW@stJK z(+#pI>@c1%+!}5u%aZF*8W`e?xcG534$+V4|>9^=EteO)L870{p{Gg`l)f$XAW|WLk2$j@{<`jI{m|+`X{Oj?vyT34L>ETcZY#? zGAC(7d6=%6s)$_570~hl_#tFyN{00Gj(X5&;oEVyqpC7q>87D ztD}vJizB~{z4PC+!9WK(G~52Sj}pC>Z)Vf@&Czd)tjpo%6x3^|)GK@mh_BVG7A>}^ z9B$&^4!SoIpe~S_#n*JWgW6xp+ib^Rt#0Artf0rbPv-QQ4pcrq+7HCFSufoPx(Q+> zb?W*2Vqe%JeU$W#2ROl4nhVj_mX{2nKS-|-%+^klY`1U=dX*3?>NA~fw)mwn+KrHN zP`qP+J3`6_KbJN<|Ma?N(dYMbqG^YWGM4f2?Y55?!Q2dV5Rj#~kE2X?@6HRhMTw3e zj(xnIVoH;q6)~cYM8;U=jd#pD@4B?37DTwtE-g5#3NN@TowN!)`EY8wwC*Cjvxe%? zMsN48b`!0oUf7DB`GOuq=z+GIe_ML2pBICsuI51XPXSlz+}m#rdHddre=}T3J7{gcq7dJMwmo2HyFbi48uUN zU~7kwXGcv2tXXBFBaboWg0>YTt(P<9ESh9(gFJifX$q6T7k1EOaOU$4!Hk52oX~`C z_WkQ}=R_sB_0dx=&@{RH=W^ZLy`7+L-{_IArzxPQODwZFJ^Ys9HK`5Pc1pGy8>u;o zIg6&lRLt`sCD!7^$!(%K6ww~e(UC;mZmD?!vBGMfkAYZ7ZIYC*=Tg=#Y@-24HNip{ zFzf}IuBvBBlqUy0MAEYbTa@UN`ilhe^~B6c%f%5GM)L6_;D?N3UKZ1k35Pm?<9HvA z0hw`}r49~c2v7!gX-umw6aK6zy&6Nb^WTZVOr zAq_J%eM#v>{0H{sF0bR;>7yzIG^@O?KWgdVr*-;>v}}qgR7FiLm{Oj4mf~l{zz;g+ z#`DLb5CTFOi}mF!F{C%2&tQ7S3Qw|oAe&N9_Tf$isiwQprFVzeG^{SUVEeTO<}7_2 zraQ~mK4CRyw%(>&P71FJ_s`YDK|t=>%s<@NiR!Jy;?sN({nS{CFP6`Q(7XZoJMHJ_ zvZuA6wN8)T0oEVJ<_3pC-QE83SKn`h{_$2$gDnndmSWGHWN%uzTW0PFc1Gw=TE{!C zFv?Y?w2P$1K-G2*_V27uZa+G@o^w_eYBh^g}xOcYbKg5bjs9kS+|*P__)|g zdC=ax_k8jd9eni0E7P%gFPWJqFb}(H!dk0z4sj|f5ka-!yYm47YNhQ3r|EJe#aOwu=`N9P4Q(Qd_mb zT#S%ziSEju?~mLoyYu#Z#;_qrfs_Pm@6{j83<3t+Q^qC4auB~j!U zlG)2hwoF_Zi{r|E3Rhy}BogAKXjt~TwmV#A4##%~?k0^CU?l~IC2$4Z z<*$;3*OWVbI=viFe6?(L^YC~-^C^baERK39zj9W8%l*cTZQg*juB)3CrG}1mN0{g1 zi397-uRk~x))_y;hFlj_KiWs$c6;%0?s7NEl+CxYe*AN(gcq>lsG8ItQPB9_eH)wn zDlJp;kYskSe2+}HhQx>BkaD1JV`?iEXLp!%eN_Zk+<1jq)RrGVHeg6Dj3Oo>odfdF zuN%b}+Zkj-#3sH(O6d@T%&xigVBclb*TG@iD|im`iTMuHUez%uUjUvfITPMI&Ld|b zbAC3yTev*F%h5&jl;Qg6%F%G%eE9X-7K9%OV}n^f_PSg@+5H?h%%t7q{pBdip@A3m z;OoaEOaHS5THO8Wm**XiwEcY!7CLS{Jv*D8zjTz^JkR2*#L6KvwbY5nR%!WoaeC{K&i|#O(@aG3Fy;gZuc7fMFl=*gJEHT!#28IznSXBa>zQl! z>Uo|xt(v~XFiLqp5NY(5;E>5_?t>TQCP~p_S2DkerrCX$&C<=;UWYHJ`h#3uMWTtk z+$W>`V~l(Z$in1TIqAZ#`sr`XTMtBNnsc8C;}|6}p%RH@SJkjF2>FR8hrli4Hw{AD zh&cpN6Egi*;<-M{<%X!Io%Xx3xx|FK1y^v4r^>cX4lut$E|S$ZJbzS~r#8?Dr%2mH zu)__7#b3M}=i$Sy%+~ z0QqRO6w?wS59(udDJjW9qX-fRwnOrTMm!$b8i>jFxE4SnYTDR?Wj(V{)j?G?uoLW& z0n+^3+9lGvCVMki)J(7iaUd}4KDLc4z0s>Et0g%@9y_%qqy}Eqi%U-f zA#N@5SdX5K*@zmxjetoB_wfZu{*h_i^oYRYSdJwh*?G09*xI6itJ#L{-Rdkn&{Uu& z;Gwg-D4)>;GtJpGzUDY*_&awsUqb>9Yvq#x*=ME6gXAP_2-lEKC-wuO5X(aDN#PvQ z4MmNcLqv$`!p1umZ~7(p>QJHSEmQaU5{V1fjUK^`-cdT!>Xik1a~I*%7_K@;OEqyl zTM+0@wxK;A)EGX&Gt(*2a>w$mHKC+lQCsmcD~ea7Qd3wZ!XhAi<|$Xxyf4@gmLGI- zXV)@b@$2mS%th-S+JVOoPq9^WCgTh^ai@Z7AQhYBuzmN<3SN_*Zis=r`D5eiu7&uQ z2^)cq_gxNkaEqPmRFT*O8<4X%gZ7{ppv(gK=l&JQml;RLxu;paPC~)D&P<{}&82g* z4qs{Gwdbib@pe8g!^B^0gkaYyp98EzOS7M(esz3~7$|-3QIgmZWAuz(>6McZ?-EcK zb3y}AUjpXv<@SX&@eb3iMrczua-ti+LBPAt{qr<8` zd>={#yhi#Lvk*1!*Lb&O3oCa&_ECHqLn%v2?Z2FNtgjgxC!R`TyQDt42DZhteGBFB zC+X}uWFGpkmC_c?WuJ)mc8Qc4wwzN8m;ICJS{P61Kte8sCD0$0aR0VzO0O+^#!lT zZ=RQ0v5RwpU}Q;!$Yk;M@O$*xos8;&mE!h~``q4-fRbx#YESr{xED;Wv@Sd7`|Tl; zd+a03Bw`YHx@NH6xE_mC5Q=fat<$wG!_qaZKlW!6zowD|!Q7HyIsi(4P5E#dL%D7W zvuE*?nOxu!u4Uj&idD8yQGL#HWYif;o=iqTmZ{0015`k>mP(qcaG0T`f2AyG{^_6t z;ku_IZQuH^a%grNudpL%tJTf0Ib`tGjw z&UW8Z*Yv^emX_vbKx^w7L9`qXH6eI-*tQo2aWDEJz+Yt9eS4 zKd8V}3v~=&%b1Evr|y9JIwI>!X~;*6rp_8cqLAUE$t<^~EVog}T~7Q>{HlZ$H4;39 zg+NqFyhBFrgzx%bydAH85JgH5IYUdfvWH*FR{D-wTT+?BkWtV!y(&%5XY%!uQm)Rh z&1?uef^e~2vp^j-t?P2P|0wGY#!YMS4mM-RP0pHA!<&RT7o*`8D4sA)W;pN)3c+k5ySlg2hR=+ zXyjb7qgBnwltW>`cB+PHjrNub{E@MsEz|T{G1B&(wCP*O`wB#*l4u3M z1c6gAh2jIkQl;ZxlX16p1A^=168ynuxa}>`1O&Z-Ufg78*YulbS*zj^N1_d%1U*gs zZ^O4nApWy^w95WEG85%pJ_r(oZUPUO2&U;I)^!L1OKeo6_Fh_JB-YqzKC{iKxDrv4 zur?7_lh8Tkfs9%3@j6ygoo0}<+>0OX(1~2kB?n>AIZ_>@cp?27k9jX$A#<3w4N#-U z=y-B=B!993_XF*O5$EA@7*zK z4Fr$rSQZ@039*&kKb{?uL<2C3c zm4V@2@j`>4siSm>gt1sBE2lSO(M`d&5z$WQ*z-a|qEs9Hp|7ZF^C(oIJT-!QIAcu% z+)3-CFZ^DX8TyrUm!}30qE=x|8@u?2RQWVVBmHGFGurR~ypT}!_9v`FM|o3e8s}h( zfykR-k)EwngubJX-QI5Z_>M45eV$|^i{)*&HEZV8QCJmq7&ccm(3^v-VOQ)IK#U9m z_q#Lt{BccQv#;gIdY@5-bv?-PueF%r@5xA7C|9$K4 z?fU!ro3C9u>OTYg+-Cn<@Y}Tx&53_C-7gCM+;{x9APQaP{=d7B7jZ83p?)CI;{8MZ zzx$XMg)eq>ehA}T`Y!yV$8%BiVoTzOXgK~q&;9>3C@unCtnhyTMiP7nyjbsF1h|++ z{Q!WXI|e_y!5>N0MU;z4!w-~3bPj<=`6Y$8i12eZ@IxK|V5a~8e#s3kieL2KKL9GJ uE-vGuH@}GWvw!}91OPCh6U~2K7n;tnya0m<#C=eJB5D*fOdgPGr*PtLEVF(}~=pZm) znnE_#jz-pwFO=MDjU2QYU9BvMGa77?M+N9^{?M8|zTcBVn z{aBX$CiLL%Q%kl!={DLlu42=-QWwW}s8dg3MQJn6H>l5A^XiI>VZp0(tTn>;6#8qB zx)9Q9tACX15zEtKs8>3*M_jVx;KA;mg5g*x-)@c%wJD3nL$7=vc(bEwuc{vJxxH&4 z4;eF;BGDxVkBO{ui0fdAffGR?@J)%hHGV{5FL$gkx;3$ zb;7#pZXTk^qA=TW%TSR{jiyH#!%^BjC|=#kPlXNIUSRL=FkpNgs2{^yJJBbsTXNPb z1;1#%F2hYGM;LPw_S$?j0zANLys$U2bYNoq@%umC;{ULV_-X1z z(akcg%m{(IV)a6?mmAi^{)5BXl-XZVXpy5RtnsQtvdH~*wqeC!XstGp@C|NnuFqA+ zQ-+YX2!tMggLr{OVX}r{wye=Szp>g*X2K$#_gw%9l3)K@%EpQD^=m&VH)`h&(Rghw zjC#o~e$eM*8FA&rk&G_5a4NZ12n?tduDIEl z<7U}PZs-o>pwoeL`pabLoJCDNV?D=J=^4~V(~5EL`VKRENowH0C0+ID?b=nfE{ixr z41_Q!)1e0qBPc^DbaJF=;hD4v{(|4LMyid_&vvUVA8%!`oPtBPq`2mj(_M8B7~abQ zgz;C)86iy}5d&CG6*LG4E(i>$t0mJP#^YjRZ=r8vW5K9zX8kWi0tIZlfbag_{;7}a zF>hr?5k3vL@f&K0|4uHWpl_lGH&wF^oquvBXxU{t8g$#xVzrHn#ty$2t}9}?v&gEo zeUdJ{{n@WY;&mc3KQUpjl5rfh{(-$m{R|2+JNBc}9PdO&Z^QK2xvjVFR;Hh}RG1I3 zDMEB)kAC``hcSYN8_{4Xn3$qf7-*qb=v(uYcOT3Y2Le^u%OuK>b%e)|MLwu^ji_#% z`j%gF#VxMIPih_)n8!ka_rBdtKEp()9>4abcI1q?58D;A?!**CZflj5)` zE;Q+B4%|FuKV-5mgBRt-dVcD4j>d8g2$eO>WrGxm8zmeT{6)PkRwFL7UIC9X5CcCR zEy3X!`-K<>hFCK97O+qI6NYIHCsgq#*(mHOjA^-w>M7CK{V!J z>pj5^l)0Jy2LF&A))7W&ngG1_cSsNrfQttK=;6;A=pQR-p(u~ic^fm1n`53X0wIKA zo*k-S5QYpfG>#@2Gp^Ab+d{!qn22kZX{;_cn6~pgYzLCxhp2V;-OE$o8hQo@;EG%5N zwa}NouMH%Km7~atpmJ@i4uyKFSTXQ9EGTv?-vOx%=GOc1x z-w(buTB?w7VfNe0NVH+5uUADTgu*={kDg2ol!=xSs=mpi?OQD{lbe5PRLh%OXKck? z+MQfk>VYah$J3oGA25pb5mthwD=k~s=~E_hHeV{RnJK$W_^p^T7&pz zmbsVWZm!LfV?ibqro#!32%N(hQ-*ZE3CgPANe>|Jy&lU=5R#j^upO0eF!#h!Ihjo9 zMA)XeR5mF?gi6fFfz1BCU2Qkiy_irvfqbmFTDX)(oRqFK!++Il`=a$pylqTfkkO82 zZQlqlAE)cg8IjW8!Y~(lKc!lxX9ekM zsZY;Sk;FON8qZ%Z^NtRvs(G}k&|at+1LnRea%_2Gfv#S zH9iTV=v_j0*nji5sshx1WHzUlMc38Qh(BRRE(UaHMazT)RW z^80Xxvn?vH6b=(tLDg~P+k4!kFhm*o1vk4sReQYH;}F7Jv*KEa)iuQ+1Y8o&@8V?U zKw|}3ma>$WTkBPk`BK3YN1lh%$@$KM#+xBenb-^v5I#UqWPdBDf6K{opc&vr5ug?> z-|gx;zDPY{?g&<#)QhrMClsiRYkM3YZX|JVa%{Cdxu0;xpK((ap!}6OG}7{&g@#-k z$CH6dpWU8`O&)5RFGv$#40Wk|Mr>Wz2deZ7DAYaBlrgcuC27`$EXv^_V<{<@J|*cR zshRgy%9xgN#qiZ^r}RfcA;{+9rujVmgfwa-dwf8|DyR`8euiXg9R=2q!*ar#@9 zT{52O!T`5i{e@MRNp^N`Lig*V5nI4U`Bzmz3}WC?0nC0190UaCFA}&@64oDc_mAO~ zX=g_0+^(v}qIrf19t_C>!iENI+OCeW>iQG{&C&xF3Mv95)jKW;lN?GQY!>9o#XPzp zlTR{aIr3~ZyN_>#n~DE>1g^rl8Ds~fsvMaV>BlgkTmzlYU86%X;cH*p?D{r?>)P6j z(=KEZBN918q^x~95_0f{C_N1c5o4SKo?cHXBfTg|JMfL; zlo<#>vZmUV=0hg*f5{w*&>}aNoItC<_lh&UiIAu4hG8k1#E&^PvaAAJQ%cu{P4^@L zsYQ;jr4Y%I#%D-y6?Y)9_+7ZVg*5v`uO<_BUH3jM zVWz3^x7{XI8kwXos@JXJR;Q2r*Rda3i=M(4HKu?yC<8ngf2@Ursh+)&p`xR`nYD?- zk9ANAw}2DTMF~hhjqy;pa#d|XnhdcBQFulDUavrgMSSG(0Jj6rY$KFHPNXeuwV=Tu zuH%`%02_Gd5^A`Libt<(TKrWzSUPGqLDE$0h9{!*>`Tlag#s3E>!xj#*qxQI2`hL+J_t{8F0JLj>Ea7=vBn^j0`B@?w0N}4$4Dx2VACv|!H6M6S*wr5?SUocRcWC&?kd_h%Y);6@GkFRspTK)1-6jGnFE$rJPL|`D zj);_NjxM|M&ZQYUo3OLVY~YzkiirjL))Lz-dwWY1x8+95P$7=WXPXF^lxU~a)Zg&Y8@MG=kq5dcGLjZ8$V1Sf`$T%%w5i}$ zPfttun{8#_h&G*^I7)c>iX`WJNMhM#a}A3dh37*T>PzOrfa2lKxA0HxAw*w8sf>WO zr@S(KPDqW+LXTO$=E{D!Tx_YM-HbYPir{5UtB=^vZP7jQCT;(S| z1=k81mU;N}yBavJW1A5VbMt*yzXh-~?E@n-aSPvUCezF(b3sjGbOJiOw>M*ld23^b zRGrA_1pHU)XG2-bLHrszNQ+1jex!>%HvE@|n~_6AvW;%%XQAfx^h;jXi!m==-PGg4 z99Ng!wkOEGdU^7#T{ZpY=Ib)tS$xwnsmF6N(jf+W{}VTenn#_r$uZ?zMD5gVqZv&k zdnLMGRWrcr**lBiCMZe{ci>DJl$$myFC$7?<$5uSqddsM@D~_Yi+Y4}z^zTyA(11E z?c^A=dSBOih9SQ-;6n-YCtCDdQ%-k71 zu`jil|N1+OXwlkUidulO1k!2?)6#{4U^aLQ*M@9xtn%XzkdvA)7l;U7i&(enED1k~ zWvmHe4x}U=lG>*b9X-RRP@W_SZzVkecLSk$Jw&Lgyr;dR68tKVYr)m5c2{A^|ME!0 zHV7(0A{1iC#yFESegf9zz=g|1QA#SoCvf*@Ke)qdV)Ri!$*atQ4|D8Jb#!T#8=7UH zDskieAu0_Br$m+uGp<;>sQF3u=$;de^T#&3#k=!8=I2XwCz$P-zaX(N53gbW1J zd9mdg$!zwD$se^_P98sM%FJbhR*5EBibmnvkbnY3Vnmr7ByJuPRSIZDp<-T|;O!%i zqS+M4^p{M$>a(Y?2@i7ctDqW-=WCtpCn=x)j$T(^m{6G|(cfW;nRsYRX{yY|^}cM3 zjz+O;+sS{dTEQeA&7SUHtt!yd$)Rb|XXx6PH}%nE>h^wZ?a0BPGC}g_ zhMl1)(%Q7kQNlwR!C6oEvY#)vICcO7wbj<%zr&L9gvI|w9_=Js2HK90l;|L`ZFSyf zE1i#h+zg+V0+O4joO+{C^0rkHOjWwDtZ@}e{M=~sp=*>ktiyO3X>z|YeTmh_y&aGm zx`fq|E0S@9@TgVC!daJCch$;|62g{vwt)%kl6g9mnA~Kf5;e?}UyWhKE{5^3<8B4LdiNJYB|+^Zwd&4qQDtv-d@S5;PUT<< ztUrlQLm})K-h3P|gA51f{f4pGw{CboXJN6B{?@~i#ZTUvhyz@Dg%9q#-7 z*0yGRi+-OkukvuQ7jt!VN%#rRaoMs(UCrni%2W&moaD$Bu>qJtgAv^;)Q-+mlEIn1 zxYjTlvuN0c6*L^Q{6_QPwwrH2AMkX2=;=(?5!{?_$k6)&>w~|Hwz67U~vw z{6EgD0}_&CMpqQ~xMR=`!)d6Lq$mip`MFug_GSLoTSPXkliDPveu{iGE+<|btUSIi z`is{W7kd!7N@%hvaGG4TO-A*Ncyq2<(-KCA1&O(0 zf=wdco=Hh5alJA*Yt#!oe9LhtESy#5r#Rt9a)L(6gfz!g>LROKMG8IZ-_T_US}6PhZgEY%Pw%jK zPy$nhkhcFw0*`+y0pb6@B|v0FC-ILY!2Od1%4OPqWSaybF2VI9M>r_&a;aE|orF8V z6WgrFaCu&njW+O3EubvhY~Io%uku>0guWKhgU%~~7TThCu7h9yLT8t-*;E**7BBY*83eUF5(LeOjQ|MWh({p6qwgqf?&ht+m%y{p2 zKUTDr%e4h_;I^;3>XaG6mQS)+rBF{=UAtA>ZutDX!(IyC)CMa)pwKTeeNk}&Y|^ew z^>VMo1?1SS`|04--{v-h)9RSJr@9V(Z zrvEh^F!*~N7&qoC_(u}p_(=jlI-vHywHKUCo|^oR>%h$b@%}%O0Q>JG;0P)F|Mgz5 z+NVlI4hjM?2O!QL{gLbbAkF_ID1V|R9Tn;T{tqR!I!h8_Q7p(51yU{z#TPkPxYH~3 z45VQ}01Wntj_-@iT;4T-*elSjqNyIi+PJObt=^qUc^SwL6(>p%0p_V#r!;hw#)@_P%7w+sjgW zh!2HYG3__4^Z>C|0|GI?7k->aI5@gl8r`u+*-AY!N`ff7U$qfj1j{-h{9tjj^5q{1 zNQoH+*m2;pkheyM2P30ViQr4khI=(hczGl5l^^pPlZ1r4?l#DQrR6KhyO`^l#d!p^ zEu-q9oc=`l>!}KV%^9&i2tpYBBFJixS-J?UvU8frRGuBghpA3qCxp`}ez>{UFrWKV zwQ{wTNZ}nf5ZY>D4oFKDkye=f+$11z>&f#Gbz{oOpe|$)clsy6T*T#K79r+f zbtnoFx|84IsfJ8yzte@ECVXC?MSgLsj-Y;okSx?u6Z9CYN~vFsqtW`Bt*1g)xb2PI z9)5rVW$|asG=yH)m0@$?0}N3ryolS2K8acPF-Lc%v)HRvA->zidT2g)72(&t`oS8N z>pJ>FBWq<1JZkYZf^#VZ%zZOo>d_?M=6Qxf&F;c87|^f{Biwday4e)bICK%oxY5rN zVU1PqFxnU>NcPit;aT_|f71zi$^P_o32gO!ZI41pV8tAYheKx3{8y4ubx?#MOg_D= zpu<^AULY?>bDxRnhS)Jw&k* zo6fzHvroKf#_aRys=h86D7UJIEny1<#kjn2^nH#wOQxQr#Q8|yXA^fLvu;s!@Jz{R zID^6YY~5Ds?3FHWi?O3dak62+oI|Zl$vFYq8JZs1GyczC`4$Q~9r!cnc<~jFiicfX z;;{(_o~pl9y8>`fe~sWBvw6U502a#x@mrqwE^Z5psr$hI2HsA!#lVC@z_*Lgk813J zqIqELb4Q3`R2>FlVe$E1K-7%az+utGH~927cBNXX?QxYQd^BuKn(Av<(27sD4}ubW z^(4cd1Tz7ZDvp~V$k#yqbC+*M3a+*bS!;FC$S!G&qq`*!Dn2ZS2-pYjeWP`e$u`lI zB!Lk2SH2^By0cKa?GcEmBoYk0(h}ygm~Y=nh?WRR1?ITDv9mu;}mV+S1pv{ODaT`yw>Gzp{M zB~efaN{Tz=WObqu8DtRm5bU9z*m+CWS-?_EHs}W)nJPoA*?a4R4cf2`p2VXcdC`_Y zP3FLa`Uz$YK&YN0RgOuhv zrkOiRy>Us4WKA{px5}D0>xK66tTvzTJ$Rh)&W}Dr2e3$szo%*MHdX%^{vTw1 z2Y+4;11T_>ze^IOyPU48y8zP!tCplLs1MTi1(EoLLSt2FzM6#AOu3iLa2Vuj#X&6Y z=-$ByOgKDE!3r0*_*X6r{GMs)C4xRBp3f8ZT0Kz99f)SLqg7*~m(OjAL&)FzRy)c$Swi_rDQBl!nmkM_cZ`JTFqJ z%a19QIF3kIl{o&*nn+pObE^IQn%r&k{xN-w?Q(#FVh4>6agT@9Q1Hr|G?qZ8U9tA@ zCos{R8(Lag$QEENi><`zQ6z1euJY1mPE^dJ9dZxPQ{Im3!1o)S(;!jn9Vo(w!*>;| z)E2aTLW1n9a7Rzdnv)o-W9Y0da#_NSellzAM9PI`83v&Ty4>?PtSlswdKrXfyy#n% zIF2f;nq9P!t3iLfA;J(FYpCo;#tw4C>NrDu#))=4a--OIWY+Jlo1ik8PU0QUWo20H zXBEqn6Qvb8Up~cBaIa_fWD4}#KZEeVZg!2sOlY194+osE7(JCG6n1BBd?$!AbGbJy zJaZW)Fz(Cita}}E!~&&fIo~vlWLXaR@)CL_eUdnA8&7S{hoM@RYYl%nX@PdGoD8xj z1uJg1)RQCVSSw8#;oa$y;~$BkfnTTLIfcm$6(9Rc+{a}U!I*sB+9s6 z*Pw}dISTHYMBvTgw+jVVmg&eQMI_ZwN*Rabs`g8nrp(bsLUT<=MOVf$hr?Fo9!F<{ z?{hfAfc7E1nl-n%(3ETTm1S*fF)WKxe0elj%V+V9Hvio?U#vL6r!m5iPb_4PZ6SP` zWJjh`{jP>S9-J??w@BAe7ze-jV5~j1Z2?O+pz&r_!bsQK6`foZML@Su_K`+k?t?{q z9^p)OWFPqI9K86FZtym#_K}_{D}A46D4)rvnwNGgYAH8MYvK@HCqkmXJj*XiA6qEyGtl=+?5fl#-DJ zLII}p;qs}Hx?<;YDT@3k_+8yjXy|=e!?h#gT3xsU!e80VZgMLz9W0&bxiE@Mz&IoK zg%?>Z7kKyU1$>m_m$MmgCLq;}-(u0}$H8&*@{T0Aqhb!^i^!u&3it3biM1!s9%?8R zb@9HGFYhQhmE>H&a36G;WH=R31zoj5ZBkt)O>-+Q*2+G-GCokrAsaSh70`?X)!<_C zbq}Rz2o3O^E?wc73?g*qD*w#hG=520Gj~%J5=!Wv;rA-dXNA%X{K$(EiFZpMI`n!j zQx-B|-C24G;Yj`DL*RwXkqyMG#Jh^pcA4ZQEGzun9Z04Scvl$?mn_d%EH?y?c&rbZ zym44mzC;gd|3-8jZ4_WNa(=&w-%$@?fBGU5navtbv+QzW4t<~MHt?!_`X%lXI`S=# zfPeteCiF>-nP@fQ_&M+Mv=h_!B^J@^9_hCp**vsVpKOVEXxc|iq;c|+dC&`xgL@Na z6}bm7a2SJ_bhP%M**={G#8-ZN+!eJ61!qA*H8T%7#OGCnxS*zB8rCEm$!Wng>!7dGLk^L)Jm6KFc|!SAxFb0cJA$j5N7hV`PQp7< zZ$4dIex&Xl)?{Q=OZbSgn47z#KcQ!;^?_FQP0x20jro%!6$o*)Z=Yf8TStUYzo~tH zaF-8u+qM2LR`HHL35$_tgcJnqC}V7MN0Lui|6E2;<0bkf`~mbowiHxbFn||BgKrx~ zT=P1OO&Z$Gh`!0+C_GnIKHY+mFt*b1Z8486s{0VrF*Rb zSsj+#aH3-CqB}uqxzCGjM?V^bsr8ROeq$5y>H=G4ahX)~r4pBYKFiaJX|{?Z`bD!+ z$~_MpOE&$u(ZE;1MrxXRvvv_YXdfj_-Tf3zjnF4fM)7o?$$kXqqeV9l*U(8BR5f;B ze^qcu(uE;D*c%l6Ec^{ec&id6APah^Ll!yt zg@4rHT{iSzbVvv2a1PuK>+d@B^p!K@0t`hN@cgYb?>sl4Lw#LcIWT=1Vi71@<70sx zk9-s|aMn@C*D{d$DgZ6fCw>LpRv_R<2(tg%Nut`b`u(F7yes z31oZnqnZT;U@ugFt^xL73n;L0G_qGTa&-JrX#@QK-8{GA8v%vbC>>smJ^?;S#mEA! z3OFc|a+1(d!J`cVqd3)Q*I8&V286MqdJ8={m}#^SXoda*(Vh)Fm(jaR1LEs;k}3X} z29#1Xd&6*3pL83P8X&ouDqgP7EM;ySrS0t7s&EIfsah{5S(uF<&CtCn@U7 z-*tS=&46Z8LPLgLvQW)Nr*vv^3BC`{Saza_KiV_ni+iNMj|$Y3ECgktdj{?cyAl9{3rGpGMUglsBU#uhZI{cdkF2*0YEtT6jj?h6qIR z3GLwQzr)Kk_#n{o*%F))A^L|U{xuHVrl8PJ1&I7dr7S9-THDaZK+fLA)`7{u#@^`9 zyHWos(gujmBU;{~g&8AY9{LiQWZ=EH5mrflC!&&Tga40HTnlOl(7S-)NF?*tW zT3SLGB+7LBgnSI~6TV}VerDn(ktrd;F2Tm94DM-JX$i{GexvXuTn%(x@d+unYlc5| zHk62wXq#wuG(pAebP*`EII$mon2Y~0&iTH3*~iPnXIZJ-4%^5^YmQ+r`&BA1c~0ln z`_+_{OK~5wJbNX=myT^CJtmgou8L-hM%R@Cxd@-~becfJGHv30Vruv%rg6*pYT0%U zl#`3tTe!_PzIaE4oGjlp ze29IznYc~NA9$9|6@n9b(Ha|m#_&D%Evl?{c9-Wld-TIck#6e5$m0NQd3!WV>uE!)Ws%I z!Rln^8`0@@&G+(2o*U{3wMewNH@nGED6#g6)nsGztD>2$f0)G#P}y4;Z2$|s;)vMg z!bh=0I+y;6SJ7`bv~ZieB2(?L=;lxa%Y~V~Ic-YuMkeXiM0ec+Tv!7>22OKlCh;fs z{Ld4vQU=YPlI1GHP0?W^&yqoWkzw~?Dn3`mjq+t@HVcEprA7MQ2v0coWMV&h*DkI` z_BkF4_C3QUR^H?&sOQDVxi&Q@GmH$R_5ntOMQ8aU9JiyYD$AvP(PqAIRON4Ym1Z3o?4CK6M)Z5OUubJLkr!`p>PdU>0)OY!J4cueEQT#_qO;^_VH&&V&W>g#jZ=efbwOkf<#S! zwV-S^6&50ymuY?q7>Dgu~8U@LvC4vd!c@ubLI!P!f>sn&O1i@$buhe8y4Bt zVt0jM`IED)ll{qqtq*a9$*13WB=HkRqdw!Tn)@C(BMTE~k@W0c65Pm(aNZ9csp%a$ z5NqF56Bhg2G;^#(>vF{8$n`_?MX2?Q!cig(u~x~g+`8eeCYbgj<&+oRuWv_=JzOI! z;{FAS6(FqtGVH%2e0~Y!ehC`K1d2KUVLx^-MIsJPE-T9q`TtdI|8Y)pM|l6z+ds`| zK*1OQW!Mkj@4i=xKmK~)wzr(*uK>SZ_52xt1F#MKbnz3o;jcH{es1`qq#qEx{^s5r zFb?odrF*0^*ncO5|GGH`+!**u$$jG=7v6u(DE}DGPp7zd1zo_1z;`e15vu{`mcK{* z_n9~_An;X&d%zD!z<_syyUPiIQGko^?@@$M?oj?xk`IgkTr7W&@bwRbzZ`Y{m=Z7w za5?xriXhtEIDm`8fdPPP?(P8y07ve>mG6B89xxzq3EMp&5$@jr-evg@Y5;IW*gYT# zpa%Z8BfPH<10Er8fz>@A8UEh@-j`be0|M78-2+k({0-oJ^%5{3aQV+YAQd3F|Lv6T zi-CXvfvaim0cilN(r*FpYioc3fs0e_0qKbU2JpUA1sD*x*5e+Kfdm-vC!YZf16M7c9UU=-lPnR^s0x(8A2)DRd2_)y^ykuhZy%@DSrp{;DP~jqW4&EfB@&W1AB1UfH}{5 zEa$(&dT_S`rZexcR{sv`K?4D1D(|t>0CC}O=lGyH05g#HSiOITb;r{IuOl$Sc#p*e zV8wqst_Kwim^!@2djAL3KOqu<2ly*Fb`J?4d;wJRU#K$R=D)HTKQ}J|5Uc-g{wKEq z-1^u2@8{OFPkw4`@b7F87yvl#zlQ+j{TuN<=u3WYayEa5^{1zWyyNU?Kxjb90S9Mu JfEFMi{|~n{oO}QP literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/excel/testskiprows.xlsb b/pandas/tests/io/data/excel/testskiprows.xlsb new file mode 100644 index 0000000000000000000000000000000000000000..a5ff4ed22e70c9d19b22488024e79dda8144a598 GIT binary patch literal 7699 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 literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/excel/times_1900.xlsb b/pandas/tests/io/data/excel/times_1900.xlsb new file mode 100644 index 0000000000000000000000000000000000000000..ceb7bccb0c66eb7e46d4c7d2f0d05f5e6aced3da GIT binary patch literal 7773 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 S002UL=uy$jjnZfUfd2ts?A??A literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/excel/times_1904.xlsb b/pandas/tests/io/data/excel/times_1904.xlsb new file mode 100644 index 0000000000000000000000000000000000000000..e426dc959da496e4d70b4f7e798c73e141a40dc8 GIT binary patch literal 7734 zcmeHMg;$i@*B%(U8>G8yC=n1rKxu}Q&LN}*X_OwiOF%$6m5>w(rMo*MBn6Q!$#1;n zmFw^O2kw1mo%da{&g^IAoU@-D&wv$xNJIb>02%-QpaVtDPB2^zi1t-N>*412h-Wvs5f2y|mr zsH_B}h0^$zlk)uG0V1M&vGJZaq zXJD~qbCn`qfs|9FOKwSLrY9kEYhgZOO%FF2Z^pDEjO;QVuMne<(Q&u&7C;4zbLH+@|hHdd0 zaXy%XmlD&r3!Sz#@sS$x?P^C8-zZsAe9Lb@vIhFy1(`axCiH0%KbeiaC|g6Yl`FUC z)Ur718Q5CZg0Ia@Cssw3VsE+SRdrytK~Q8PPK}QTWwVmWB(iIBqYkzU&#ji~Oz757 z=olXD%zBI)%ZR&h=*Lx$A_>vz(n14w1t-}He^7)TaEhgrD!Ua(!smMx;b)Oeb6~W{ zgy47^bASO`#`g-Js;)w^AU%Y(jUdX)gF7#WUP;5JIbPJy0wVaCeO9&cR?t!(9Jp;d^0ej#7UYRe#fhcI`>c z-QkAPbcuH9fg#o?4q?Xt123PqUhA=H)M1TBs^-L`9G}(q2SZd(#8f%kDtn)6wMerC zXGy6%&5U}Yo)}GbWi8P-r14B$aw-)S+&{eDKUtk;SF%ProIzOcY=OL=Hg!vggN9E} zR>&xa9D}L=BjWShv?$qm(pwa}V#8~$+1F+5p@g^O19TCM*6z6IBkC^TIL~F9cb17B zge(vQ!Mebd#iB1@;WULl@TLEgC!0qHNvH6cIyg_r0cZ&BwmiR6#SQ9cV+w`ZaGO51 z|C2Td@I(h^+rRrNR%`z5HjUo{{f@v_3AC)ddPUj#``>(HYL%*micBkqKd`X-yNviM z@Fb>leK^@)I$F!wX+vPBZsuUEphS8~X#0f%xomv2ABbl9wPf4>Auzp2Vr z@M(t6e1i{M_<;M(-#y0K0%Bz>U@8Grl<-ED!T~bG2*KitC2Htk=y>EHGTuJOiYk)4 z@Lgm!6JxNs8N_(a(khudlLcetDM%aC#>)uGMeOgg>@w>@T5=9D4dTm`#g9?8=mLbT z0>%qLRs4K?W(mlti1gWHWb%h(Wb_iszW!l24}!cO1l@^Xr$vYfS zVAHWxRqGOdN31XXoI%QEf{L{Zb;;UqC|Z5c6rF-KD+J?6q7SwZ+IR_o)e@zJDlBg{R}#l9G*ClQn0t+Ko4U)_|m$SvZKGCVnw zwmqh+H}SNVRDeETF`D`Gn)gAyuhP8v^qh_QuBmio848rS%+P; zLQ5ECXIh}InLdm&{mM?$>7(9q(`&L;#(C_s0H72@lREE(oRI^A0kg>}#b+m93t#VS zB~l05C)SRGE{%dl)5r-x+@2^Y0$!WMyvd z?DR*F`t1$quMlNB*lZ1_Cd%S@=8mzmQQ9GIM~M2ANsP?~wM6CfHi5)QbNT(_BfGCt zdr7DFvaYHEHRo8vqir*7bir*xB7>EFJH3-%L|NIoa3;GT#w>oL4(=O+Ri;HuI-UfY z#3Vokwhbwpa!lj|X6B{37p6Gc)_s-AR%OER;x%7W2cl81RPxDR`ia%yjG3z(?bC?5 zr{=U32?(K36?D&DYc!T*q=R-CL}y+FdgPlfuDeYM3i?t%y*wOsK>ZcRU}c-juTcPi zWs2|J$8Q{SwlcRf=lSgp`rdtP=_o-dWXRq#?9n`U1~n6PC4cDhYO0*VYV<+5-q^DH z-T9Xmu9kxQ%9|9{(Xz53{F^(4-&JGtE!0 zUtT4J7nC0KUr06h{GJ>fH3ji>gQ*gnrS!&^9gd>X{k&fX7=| zWDlE;kDcYc{2!#`y?p`N;dhUCK87hyDAk`LOoSqOr_d%Kt(ThcGhSsBsw4A-l{7Ug zE{K!3;j_Eo!E%YMp+PeR8@{@N6{a)rAbundDIOLS%jW-tyGq=urp$Kz0^7HUuXJJO zD!kuRkqfcP;5Lc~;hse<``+4y-t>Ch&I1Yn*W2Lm}cLr%4zR$%IMv+znQ|Gg}l; z2J~!9Y$0Xs3KFZY3So;Luh0+Q_2NbW3`qnLN5-bGK*GGbmZ+mT{7rG0gx3g2tRrVK zYcPC{prfAF)_d--MZ}i|`{s}3Z2WV1tg=PsgPX=VBn%hw>BP;Tx_7a0 zI-Ii{d{5U9=Mzp8jQ&$E^xpZwm*`;{l@A`@PQ$GmIG21bKgAn)Up0`U9aX=%ZckG2 zes;Xl{wU+>YHk_hG_h%!-cy=^ewfQK;MfDH=2mBIN_@F6O*?DbSXL9=ZmIFyqby*9 zU}rJLBva|ZHii|KX499-^Qh8Ra34`gj3+|`UXkPpCyS0QcG4YsvHyn}=n-5=NPs4WGYA3kshMv0OSM;eA& zWYZ^3qPy+830tZwDkk{_?Hulfbofk;-3l!A&Mta8FX&Rw@zQov?+t?1^NE2_t@l_b zG`5Sg?j$?-g)bZly(T{_96@(Vb{2ZAzb(}tXRAu#PUd949PJ;Y=AxP@NGQ)r;dfL| zd1ugaEO4tSJD(p_D~@I<4o`ej9t8o18*gfewt4)aMqn!*3-8k8-G0Uxwl5Od0Sd_% z{fF>oEq$rlzpBRK9A#vHAUC^qjo^vS;XI?f4hl~; z(nsY)hVbzAZd2#6y_u2+y;9=JBJ)I1)4L#f+D*0Slw@RwN2?qrqgP|5LYnVGEJXSH zxco&vrfGjj!g4*6pwFG@;HxS!G0bCYGUe*tT(I)fzMd<+UODqx@D&m!2CQhYds>rh?LXlMAY{|;B( zQeaB+v`b$cUcpzbBrC0M5_@vBQkVyOA!?20rhSCy2e$kbo~{H_^6Npx;d69-aIvy8 zl4q?B3E77HhP!@Yj1;N-{Y_jXES!8diJGP(-iDxD|8u(oqZp~ng^9FPlOFPc7uFdl zvf!y`4OX;iSPi6Nhsff{d8dL?r>6^|A!+bJySj5F=1uIjpUqR~2^g)&zD{li1#25} z^=|MnawPEX3elJT4am278=J+9bWd9lOx2!72&lAnt>5k`roHtlaX!ZK8FU!$(iCJN zSosQI5?GS?GV#*pmBT>EgllnJd!$xArF6M1h;t37ia4q0P+v^T;>qr5@qu%gVl!k% z3M#WZ7i@v@7^vgbjYBPaTee;}sJWYa1~O7yY%@A6_l#?{n8$sje>L5qW@3wTPrRV= z;8P#*`q+|;i0ILqWt;k%v2ncVcqR;m(JfjtM6>ti9NzdHohNidv%Ak*BiJ6tp}$`v zAhYSXWXA zA^d@=0O=<%G*&Bf$Y)4GeJj>7%nFrO83B}OjFN*9CI(K%XAYKj1MyXX_zEjPe7t)g z0mW5gQ!}ucdru$G5r7Iq)K;M2hehHk4ze4fg@P#RBt`QYAbVFeH?2+G^L{`e{<$qY8qFdJS3I1BKq#$xo-zzG&{1&YrHEDJeE?1so~c zde>ZeZ`@UEqzpK^iq^#Snf->R&ckBTXrVbgX*lN|QIr(^WOIcCDVu^fT5s1}9+c8E{X3pw9UIF*v*WMY-U!+1c$sa7j9 z-dbDSoK&(~@V)*;Ie*7(0*?+pLj@m1@BuI9ovch8%^~W}j*smvf2(s-2D_TOnwkJD zEe%+l66^tV@pMUa&rJ~iN6oxGDMAQ_pRftMX2yqimJp~J*b(aB#A60^H2*_6{jZ^f z@3m)~qza6eENF-M3U#Cs+%v_8{zkeVXOkd@Eb?vsWlgN)Y?Wq>57ZA^M}T_vRn=NdWE3@lDJe~Y0hp6&lVf#x^TsmMQTBj`Cc>?P+C4>iwa91IwAl=C zl?X-RF5?Kotzb#)7IH;If1f%MXS*x0A*LJ)gBh43RAe)1I_>Dw(0r;RtB?|*K)iD6 zH^PhePdzu%H@~? zx%+Qz#7LYm01=rs5fK#Q`=HCm8ztx^82}pv2Z#S?06&pGuCzEw=qxW*;12MZ z25XMu&Q~y&U$LpI=;0d^gt!_@rF^rj3dWG)*saOvn%ItM*O|zbXYOaxa&uH7Mu#!O z?cmVWY$9YN3LDbn=k7CJjc1$~jD*(9X1?;22<)qFC@uM9B8|9ryfS5|3o-GWC%=qDExOKKQ z>3Yd^i`s-hi#~{^GIRum1;dcbR`fL#-y)wUQc<}Stu$!r*@zY2(Z1cm!0JI=_>i}C zM5x1j>{Wp#ZlWpo(B)FKLHJUEB$)&IaO##KZ3nrnI{$8eHIG&>~oZj#Cc#hCaf0<$; zjN)u~w4m?aUQiW&60}$~(3>?=!z|U$<1jJ^JnBmA^G4Ho%e<8(?r}vD)ajG%U28bc z-IE%(;3A~Hf01+qg&)N4{6RqEf@gtW{|WZzasB!H#b07zg`WX_*5dy({Qj(ibK-Bx z{7u83#mT=LF2UR4|1DMC#JMR}{Xil||0^Q=DR$m8zN!8EFh;}pJyHIp1l=^fDPjCD z^~C(^d;dQ{<0jxuMc@aZH`Z@}H}!#=05{979{{>=h2iJe_@fBBiE^`A_<^zkFCKnX zJKRM0xj6V?4*-Y}0|5Uh6>gf}4C+4sT1js%<7SAziS=`Q{(%GlNW%-$zkbRe(Hg9P V40k;M01N)2f_pCzuFU`d{|ED|w^{%I literal 0 HcmV?d00001 diff --git a/pandas/tests/io/excel/conftest.py b/pandas/tests/io/excel/conftest.py index 6ec2f477a442d..0d540cc3822f1 100644 --- a/pandas/tests/io/excel/conftest.py +++ b/pandas/tests/io/excel/conftest.py @@ -33,7 +33,7 @@ def df_ref(datapath): return df_ref -@pytest.fixture(params=[".xls", ".xlsx", ".xlsm", ".ods"]) +@pytest.fixture(params=[".xls", ".xlsx", ".xlsm", ".ods", ".xlsb"]) def read_ext(request): """ Valid extensions for reading Excel files. diff --git a/pandas/tests/io/excel/test_readers.py b/pandas/tests/io/excel/test_readers.py index e4b7d683b4c3b..d6fcaa7104135 100644 --- a/pandas/tests/io/excel/test_readers.py +++ b/pandas/tests/io/excel/test_readers.py @@ -57,6 +57,13 @@ def ignore_xlrd_time_clock_warning(): pytest.mark.filterwarnings("ignore:.*(tree\\.iter|html argument)"), ], ), + pytest.param( + None, + marks=[ + td.skip_if_no("pyxlsb"), + pytest.mark.filterwarnings("ignore:.*(tree\\.iter|html argument)"), + ], + ), pytest.param("odf", marks=td.skip_if_no("odf")), ] ) @@ -73,12 +80,17 @@ def cd_and_set_engine(self, engine, datapath, monkeypatch, read_ext): """ Change directory and set engine for read_excel calls. """ - if engine == "openpyxl" and read_ext == ".xls": + if engine == "openpyxl" and read_ext in (".xls", ".xlsb"): pytest.skip() if engine == "odf" and read_ext != ".ods": pytest.skip() if read_ext == ".ods" and engine != "odf": pytest.skip() + if engine == "pyxlsb" and read_ext != ".xlsb": + pytest.skip() + if read_ext == ".xlsb" and engine != "pyxlsb": + pytest.skip() + func = partial(pd.read_excel, engine=engine) monkeypatch.chdir(datapath("io", "data", "excel")) @@ -813,7 +825,9 @@ def cd_and_set_engine(self, engine, datapath, monkeypatch, read_ext): pytest.skip() if read_ext == ".ods" and engine != "odf": pytest.skip() - if engine == "openpyxl" and read_ext == ".xls": + if engine == "openpyxl" and read_ext in (".xls", ".xlsb"): + pytest.skip() + if engine == "pyxlsb" and read_ext != ".xlsb": pytest.skip() func = partial(pd.ExcelFile, engine=engine) From d9be281fa57dba8256e480a7802c8b649f874fb6 Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Thu, 28 Nov 2019 18:15:22 +0100 Subject: [PATCH 04/25] style fixes --- pandas/io/excel/_pyxlsb.py | 2 +- pandas/tests/io/excel/test_readers.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/io/excel/_pyxlsb.py b/pandas/io/excel/_pyxlsb.py index 667ac23426a55..fe96d4b5a883c 100644 --- a/pandas/io/excel/_pyxlsb.py +++ b/pandas/io/excel/_pyxlsb.py @@ -8,7 +8,7 @@ class _PyxlsbReader(_BaseExcelReader): - def __init__(self, filepath_or_buffer: FilePathOrBuffer) -> None: + def __init__(self, filepath_or_buffer: FilePathOrBuffer): """Reader using pyxlsb engine. Parameters diff --git a/pandas/tests/io/excel/test_readers.py b/pandas/tests/io/excel/test_readers.py index d6fcaa7104135..1dd8525f187fe 100644 --- a/pandas/tests/io/excel/test_readers.py +++ b/pandas/tests/io/excel/test_readers.py @@ -91,7 +91,6 @@ def cd_and_set_engine(self, engine, datapath, monkeypatch, read_ext): if read_ext == ".xlsb" and engine != "pyxlsb": pytest.skip() - func = partial(pd.read_excel, engine=engine) monkeypatch.chdir(datapath("io", "data", "excel")) monkeypatch.setattr(pd, "read_excel", func) From 8bf8c784c4ca1538a6872c156e414902c72a600e Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Thu, 28 Nov 2019 18:17:52 +0100 Subject: [PATCH 05/25] documentation --- doc/source/user_guide/io.rst | 23 +++++++++++++++++++++++ doc/source/whatsnew/v1.0.0.rst | 1 + 2 files changed, 24 insertions(+) diff --git a/doc/source/user_guide/io.rst b/doc/source/user_guide/io.rst index fa47a5944f7bf..9c00b68802940 100644 --- a/doc/source/user_guide/io.rst +++ b/doc/source/user_guide/io.rst @@ -3225,6 +3225,29 @@ OpenDocument spreadsheets match what can be done for `Excel files`_ using Currently pandas only supports *reading* OpenDocument spreadsheets. Writing is not implemented. +.. _io.xlsb: + +Binary Excel (.xlsb) files +------------------------- + +.. versionadded:: 1.0.0 + +The :func:`~pandas.read_excel` method can also read binary Excel files +using the ``pyxlsb`` module. The semantics and features for reading +binary Excel files match what can be done for `Excel files`_ using +``engine='pyxlsb'``. + +.. code-block:: python + + # Returns a DataFrame + pd.read_excel('path_to_file.xlsb', engine='pyxlsb') + +.. note:: + + Currently pandas only supports *reading* binary Excel files. Writing + is not implemented. + + .. _io.clipboard: Clipboard diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index f231c2b31abb1..351ec1c01b1ca 100644 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -125,6 +125,7 @@ Other enhancements - Roundtripping DataFrames with nullable integer or string data types to parquet (:meth:`~DataFrame.to_parquet` / :func:`read_parquet`) using the `'pyarrow'` engine now preserve those data types with pyarrow >= 1.0.0 (:issue:`20612`). +- :func:`read_excel` now can read binary Excel (xlsb) files by passing ``engine='pyxlsb'``. Build Changes ^^^^^^^^^^^^^ From cd95dcef34ce0f0560c984b424fff98ac6a993cc Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Thu, 28 Nov 2019 18:23:19 +0100 Subject: [PATCH 06/25] forgot place to document --- doc/source/user_guide/io.rst | 3 ++- doc/source/whatsnew/v1.0.0.rst | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/source/user_guide/io.rst b/doc/source/user_guide/io.rst index 9c00b68802940..449e173060c27 100644 --- a/doc/source/user_guide/io.rst +++ b/doc/source/user_guide/io.rst @@ -2764,7 +2764,8 @@ Excel files The :func:`~pandas.read_excel` method can read Excel 2003 (``.xls``) files using the ``xlrd`` Python module. Excel 2007+ (``.xlsx``) files -can be read using either ``xlrd`` or ``openpyxl``. +can be read using either ``xlrd`` or ``openpyxl``. Binary Excel (``.xlsb``) +files can be read using ``pyxlsb``. The :meth:`~DataFrame.to_excel` instance method is used for saving a ``DataFrame`` to Excel. Generally the semantics are similar to working with :ref:`csv` data. diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index 351ec1c01b1ca..9a4bfc9a0c51c 100644 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -125,7 +125,7 @@ Other enhancements - Roundtripping DataFrames with nullable integer or string data types to parquet (:meth:`~DataFrame.to_parquet` / :func:`read_parquet`) using the `'pyarrow'` engine now preserve those data types with pyarrow >= 1.0.0 (:issue:`20612`). -- :func:`read_excel` now can read binary Excel (xlsb) files by passing ``engine='pyxlsb'``. +- :func:`read_excel` now can read binary Excel (``.xlsb``) files by passing ``engine='pyxlsb'``. Build Changes ^^^^^^^^^^^^^ From 7a7390d443d37c53fd2a9272074b3e304fc3b04a Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Sat, 30 Nov 2019 15:25:15 +0100 Subject: [PATCH 07/25] Fixed test issue with XLRDError --- pandas/tests/io/excel/test_readers.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pandas/tests/io/excel/test_readers.py b/pandas/tests/io/excel/test_readers.py index 1dd8525f187fe..9cd1ad4dde778 100644 --- a/pandas/tests/io/excel/test_readers.py +++ b/pandas/tests/io/excel/test_readers.py @@ -58,7 +58,7 @@ def ignore_xlrd_time_clock_warning(): ], ), pytest.param( - None, + "pyxlsb", marks=[ td.skip_if_no("pyxlsb"), pytest.mark.filterwarnings("ignore:.*(tree\\.iter|html argument)"), @@ -80,7 +80,7 @@ def cd_and_set_engine(self, engine, datapath, monkeypatch, read_ext): """ Change directory and set engine for read_excel calls. """ - if engine == "openpyxl" and read_ext in (".xls", ".xlsb"): + if engine == "openpyxl" and read_ext == ".xls": pytest.skip() if engine == "odf" and read_ext != ".ods": pytest.skip() @@ -824,17 +824,19 @@ def cd_and_set_engine(self, engine, datapath, monkeypatch, read_ext): pytest.skip() if read_ext == ".ods" and engine != "odf": pytest.skip() - if engine == "openpyxl" and read_ext in (".xls", ".xlsb"): + if engine == "openpyxl" and read_ext == ".xls": pytest.skip() if engine == "pyxlsb" and read_ext != ".xlsb": pytest.skip() + if read_ext == ".xlsb" and engine != "pyxlsb": + pytest.skip() + func = partial(pd.ExcelFile, engine=engine) monkeypatch.chdir(datapath("io", "data", "excel")) monkeypatch.setattr(pd, "ExcelFile", func) def test_excel_passes_na(self, read_ext): - with pd.ExcelFile("test4" + read_ext) as excel: parsed = pd.read_excel( excel, "Sheet1", keep_default_na=False, na_values=["apple"] From 248ac12ab9b1e6ab596ffa7635b3bc9e6d83e19c Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Sat, 30 Nov 2019 18:34:33 +0100 Subject: [PATCH 08/25] Fix for unnamed column issue --- pandas/io/excel/_pyxlsb.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/io/excel/_pyxlsb.py b/pandas/io/excel/_pyxlsb.py index fe96d4b5a883c..8249f5fdaef84 100644 --- a/pandas/io/excel/_pyxlsb.py +++ b/pandas/io/excel/_pyxlsb.py @@ -51,7 +51,8 @@ def get_sheet_by_index(self, index: int): def _convert_cell(self, cell, convert_float: bool) -> Scalar: # Todo: there is no way to distinguish between floats and datetimes in pyxlsb # This means that there is no way to read datetime types from an xlsb file yet - + if cell.v is None: + return "" # Prevents non-named columns from not showing up as Unnamed: i if isinstance(cell.v, float) and convert_float: val = int(cell.v) if val == cell.v: From 6ea78ded583bf07147338ebff34c947ff8180552 Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Sun, 1 Dec 2019 13:33:25 +0100 Subject: [PATCH 09/25] style fix --- pandas/tests/io/excel/test_readers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/tests/io/excel/test_readers.py b/pandas/tests/io/excel/test_readers.py index 9cd1ad4dde778..5f6f02664ac69 100644 --- a/pandas/tests/io/excel/test_readers.py +++ b/pandas/tests/io/excel/test_readers.py @@ -831,7 +831,6 @@ def cd_and_set_engine(self, engine, datapath, monkeypatch, read_ext): if read_ext == ".xlsb" and engine != "pyxlsb": pytest.skip() - func = partial(pd.ExcelFile, engine=engine) monkeypatch.chdir(datapath("io", "data", "excel")) monkeypatch.setattr(pd, "ExcelFile", func) From 44c54392e5265e9cc24f61ce22beb2f05fc24644 Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Sun, 1 Dec 2019 13:49:16 +0100 Subject: [PATCH 10/25] line up with upstream master --- doc/source/development/meeting.rst | 32 ++ pandas/core/arrays/boolean.py | 745 +++++++++++++++++++++++++ pandas/tests/arrays/test_boolean.py | 509 +++++++++++++++++ pandas/tests/extension/test_boolean.py | 333 +++++++++++ 4 files changed, 1619 insertions(+) create mode 100644 doc/source/development/meeting.rst create mode 100644 pandas/core/arrays/boolean.py create mode 100644 pandas/tests/arrays/test_boolean.py create mode 100644 pandas/tests/extension/test_boolean.py diff --git a/doc/source/development/meeting.rst b/doc/source/development/meeting.rst new file mode 100644 index 0000000000000..1d19408692cda --- /dev/null +++ b/doc/source/development/meeting.rst @@ -0,0 +1,32 @@ +.. _meeting: + +================== +Developer Meetings +================== + +We hold regular developer meetings on the second Wednesday +of each month at 18:00 UTC. These meetings and their minutes are open to +the public. All are welcome to join. + +Minutes +------- + +The minutes of past meetings are available in `this Google Document `__. + +Calendar +-------- + +This calendar shows all the developer meetings. + +.. raw:: html + + + +You can subscribe to this calendar with the following links: + +* `iCal `__ +* `Google calendar `__ + +Additionally, we'll sometimes have one-off meetings on specific topics. +These will be published on the same calendar. + diff --git a/pandas/core/arrays/boolean.py b/pandas/core/arrays/boolean.py new file mode 100644 index 0000000000000..c118b6fe26549 --- /dev/null +++ b/pandas/core/arrays/boolean.py @@ -0,0 +1,745 @@ +import numbers +from typing import TYPE_CHECKING, Type +import warnings + +import numpy as np + +from pandas._libs import lib +from pandas.compat import set_function_name + +from pandas.core.dtypes.base import ExtensionDtype +from pandas.core.dtypes.cast import astype_nansafe +from pandas.core.dtypes.common import ( + is_bool_dtype, + is_extension_array_dtype, + is_float, + is_float_dtype, + is_integer, + is_integer_dtype, + is_list_like, + is_scalar, + pandas_dtype, +) +from pandas.core.dtypes.dtypes import register_extension_dtype +from pandas.core.dtypes.generic import ABCDataFrame, ABCIndexClass, ABCSeries +from pandas.core.dtypes.missing import isna, notna + +from pandas.core import nanops, ops +from pandas.core.algorithms import take +from pandas.core.arrays import ExtensionArray, ExtensionOpsMixin + +if TYPE_CHECKING: + from pandas._typing import Scalar + + +@register_extension_dtype +class BooleanDtype(ExtensionDtype): + """ + Extension dtype for boolean data. + + .. versionadded:: 1.0.0 + + .. warning:: + + BooleanDtype is considered experimental. The implementation and + parts of the API may change without warning. + + Attributes + ---------- + None + + Methods + ------- + None + + Examples + -------- + >>> pd.BooleanDtype() + BooleanDtype + """ + + @property + def na_value(self) -> "Scalar": + """ + BooleanDtype uses :attr:`numpy.nan` as the missing NA value. + + .. warning:: + + `na_value` may change in a future release. + """ + return np.nan + + @property + def type(self) -> Type: + return np.bool_ + + @property + def kind(self) -> str: + return "b" + + @property + def name(self) -> str: + """ + The alias for BooleanDtype is ``'boolean'``. + """ + return "boolean" + + @classmethod + def construct_from_string(cls, string: str) -> ExtensionDtype: + if string == "boolean": + return cls() + return super().construct_from_string(string) + + @classmethod + def construct_array_type(cls) -> "Type[BooleanArray]": + return BooleanArray + + def __repr__(self) -> str: + return "BooleanDtype" + + @property + def _is_boolean(self) -> bool: + return True + + +def coerce_to_array(values, mask=None, copy: bool = False): + """ + Coerce the input values array to numpy arrays with a mask. + + Parameters + ---------- + values : 1D list-like + mask : bool 1D array, optional + copy : bool, default False + if True, copy the input + + Returns + ------- + tuple of (values, mask) + """ + if isinstance(values, BooleanArray): + if mask is not None: + raise ValueError("cannot pass mask for BooleanArray input") + values, mask = values._data, values._mask + if copy: + values = values.copy() + mask = mask.copy() + return values, mask + + mask_values = None + if isinstance(values, np.ndarray) and values.dtype == np.bool_: + if copy: + values = values.copy() + else: + # TODO conversion from integer/float ndarray can be done more efficiently + # (avoid roundtrip through object) + values_object = np.asarray(values, dtype=object) + + inferred_dtype = lib.infer_dtype(values_object, skipna=True) + integer_like = ("floating", "integer", "mixed-integer-float") + if inferred_dtype not in ("boolean", "empty") + integer_like: + raise TypeError("Need to pass bool-like values") + + mask_values = isna(values_object) + values = np.zeros(len(values), dtype=bool) + values[~mask_values] = values_object[~mask_values].astype(bool) + + # if the values were integer-like, validate it were actually 0/1's + if inferred_dtype in integer_like: + if not np.all( + values[~mask_values].astype(float) + == values_object[~mask_values].astype(float) + ): + raise TypeError("Need to pass bool-like values") + + if mask is None and mask_values is None: + mask = np.zeros(len(values), dtype=bool) + elif mask is None: + mask = mask_values + else: + if isinstance(mask, np.ndarray) and mask.dtype == np.bool_: + if mask_values is not None: + mask = mask | mask_values + else: + if copy: + mask = mask.copy() + else: + mask = np.array(mask, dtype=bool) + if mask_values is not None: + mask = mask | mask_values + + if not values.ndim == 1: + raise ValueError("values must be a 1D list-like") + if not mask.ndim == 1: + raise ValueError("mask must be a 1D list-like") + + return values, mask + + +class BooleanArray(ExtensionArray, ExtensionOpsMixin): + """ + Array of boolean (True/False) data with missing values. + + This is a pandas Extension array for boolean data, under the hood + represented by 2 numpy arrays: a boolean array with the data and + a boolean array with the mask (True indicating missing). + + To construct an BooleanArray from generic array-like input, use + :func:`pandas.array` specifying ``dtype="boolean"`` (see examples + below). + + .. versionadded:: 1.0.0 + + .. warning:: + + BooleanArray is considered experimental. The implementation and + parts of the API may change without warning. + + Parameters + ---------- + values : numpy.ndarray + A 1-d boolean-dtype array with the data. + mask : numpy.ndarray + A 1-d boolean-dtype array indicating missing values (True + indicates missing). + copy : bool, default False + Whether to copy the `values` and `mask` arrays. + + Attributes + ---------- + None + + Methods + ------- + None + + Returns + ------- + BooleanArray + + Examples + -------- + Create an BooleanArray with :func:`pandas.array`: + + >>> pd.array([True, False, None], dtype="boolean") + + [True, False, NaN] + Length: 3, dtype: boolean + """ + + def __init__(self, values: np.ndarray, mask: np.ndarray, copy: bool = False): + if not (isinstance(values, np.ndarray) and values.dtype == np.bool_): + raise TypeError( + "values should be boolean numpy array. Use " + "the 'array' function instead" + ) + if not (isinstance(mask, np.ndarray) and mask.dtype == np.bool_): + raise TypeError( + "mask should be boolean numpy array. Use " + "the 'array' function instead" + ) + if not values.ndim == 1: + raise ValueError("values must be a 1D array") + if not mask.ndim == 1: + raise ValueError("mask must be a 1D array") + + if copy: + values = values.copy() + mask = mask.copy() + + self._data = values + self._mask = mask + self._dtype = BooleanDtype() + + @property + def dtype(self): + return self._dtype + + @classmethod + def _from_sequence(cls, scalars, dtype=None, copy: bool = False): + if dtype: + assert dtype == "boolean" + values, mask = coerce_to_array(scalars, copy=copy) + return BooleanArray(values, mask) + + @classmethod + def _from_factorized(cls, values, original: "BooleanArray"): + return cls._from_sequence(values, dtype=original.dtype) + + def _formatter(self, boxed=False): + def fmt(x): + if isna(x): + return "NaN" + return str(x) + + return fmt + + def __getitem__(self, item): + if is_integer(item): + if self._mask[item]: + return self.dtype.na_value + return self._data[item] + return type(self)(self._data[item], self._mask[item]) + + def _coerce_to_ndarray(self, force_bool: bool = False): + """ + Coerce to an ndarary of object dtype or bool dtype (if force_bool=True). + + Parameters + ---------- + force_bool : bool, default False + If True, return bool array or raise error if not possible (in + presence of missing values) + """ + if force_bool: + if not self.isna().any(): + return self._data + else: + raise ValueError( + "cannot convert to bool numpy array in presence of missing values" + ) + data = self._data.astype(object) + data[self._mask] = self._na_value + return data + + __array_priority__ = 1000 # higher than ndarray so ops dispatch to us + + def __array__(self, dtype=None): + """ + the array interface, return my values + We return an object array here to preserve our scalar values + """ + if dtype is not None: + if is_bool_dtype(dtype): + return self._coerce_to_ndarray(force_bool=True) + # TODO can optimize this to not go through object dtype for + # numeric dtypes + arr = self._coerce_to_ndarray() + return arr.astype(dtype, copy=False) + # by default (no dtype specified), return an object array + return self._coerce_to_ndarray() + + def __arrow_array__(self, type=None): + """ + Convert myself into a pyarrow Array. + """ + import pyarrow as pa + + return pa.array(self._data, mask=self._mask, type=type) + + _HANDLED_TYPES = (np.ndarray, numbers.Number, bool, np.bool_) + + def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): + # For BooleanArray inputs, we apply the ufunc to ._data + # and mask the result. + if method == "reduce": + # Not clear how to handle missing values in reductions. Raise. + raise NotImplementedError("The 'reduce' method is not supported.") + out = kwargs.get("out", ()) + + for x in inputs + out: + if not isinstance(x, self._HANDLED_TYPES + (BooleanArray,)): + return NotImplemented + + # for binary ops, use our custom dunder methods + result = ops.maybe_dispatch_ufunc_to_dunder_op( + self, ufunc, method, *inputs, **kwargs + ) + if result is not NotImplemented: + return result + + mask = np.zeros(len(self), dtype=bool) + inputs2 = [] + for x in inputs: + if isinstance(x, BooleanArray): + mask |= x._mask + inputs2.append(x._data) + else: + inputs2.append(x) + + def reconstruct(x): + # we don't worry about scalar `x` here, since we + # raise for reduce up above. + + if is_bool_dtype(x.dtype): + m = mask.copy() + return BooleanArray(x, m) + else: + x[mask] = np.nan + return x + + result = getattr(ufunc, method)(*inputs2, **kwargs) + if isinstance(result, tuple): + tuple(reconstruct(x) for x in result) + else: + return reconstruct(result) + + def __iter__(self): + for i in range(len(self)): + if self._mask[i]: + yield self.dtype.na_value + else: + yield self._data[i] + + def take(self, indexer, allow_fill=False, fill_value=None): + # we always fill with False internally + # to avoid upcasting + data_fill_value = False if isna(fill_value) else fill_value + result = take( + self._data, indexer, fill_value=data_fill_value, allow_fill=allow_fill + ) + + mask = take(self._mask, indexer, fill_value=True, allow_fill=allow_fill) + + # if we are filling + # we only fill where the indexer is null + # not existing missing values + # TODO(jreback) what if we have a non-na float as a fill value? + if allow_fill and notna(fill_value): + fill_mask = np.asarray(indexer) == -1 + result[fill_mask] = fill_value + mask = mask ^ fill_mask + + return type(self)(result, mask, copy=False) + + def copy(self): + data, mask = self._data, self._mask + data = data.copy() + mask = mask.copy() + return type(self)(data, mask, copy=False) + + def __setitem__(self, key, value): + _is_scalar = is_scalar(value) + if _is_scalar: + value = [value] + value, mask = coerce_to_array(value) + + if _is_scalar: + value = value[0] + mask = mask[0] + + self._data[key] = value + self._mask[key] = mask + + def __len__(self): + return len(self._data) + + @property + def nbytes(self): + return self._data.nbytes + self._mask.nbytes + + def isna(self): + return self._mask + + @property + def _na_value(self): + return self._dtype.na_value + + @classmethod + def _concat_same_type(cls, to_concat): + data = np.concatenate([x._data for x in to_concat]) + mask = np.concatenate([x._mask for x in to_concat]) + return cls(data, mask) + + def astype(self, dtype, copy=True): + """ + Cast to a NumPy array or ExtensionArray with 'dtype'. + + Parameters + ---------- + dtype : str or dtype + Typecode or data-type to which the array is cast. + copy : bool, default True + Whether to copy the data, even if not necessary. If False, + a copy is made only if the old dtype does not match the + new dtype. + + Returns + ------- + array : ndarray or ExtensionArray + NumPy ndarray, BooleanArray or IntergerArray with 'dtype' for its dtype. + + Raises + ------ + TypeError + if incompatible type with an BooleanDtype, equivalent of same_kind + casting + """ + dtype = pandas_dtype(dtype) + + if isinstance(dtype, BooleanDtype): + values, mask = coerce_to_array(self, copy=copy) + return BooleanArray(values, mask, copy=False) + + if is_bool_dtype(dtype): + # astype_nansafe converts np.nan to True + if self.isna().any(): + raise ValueError("cannot convert float NaN to bool") + else: + return self._data.astype(dtype, copy=copy) + if is_extension_array_dtype(dtype) and is_integer_dtype(dtype): + from pandas.core.arrays import IntegerArray + + return IntegerArray( + self._data.astype(dtype.numpy_dtype), self._mask.copy(), copy=False + ) + # coerce + data = self._coerce_to_ndarray() + return astype_nansafe(data, dtype, copy=None) + + def value_counts(self, dropna=True): + """ + Returns a Series containing counts of each category. + + Every category will have an entry, even those with a count of 0. + + Parameters + ---------- + dropna : bool, default True + Don't include counts of NaN. + + Returns + ------- + counts : Series + + See Also + -------- + Series.value_counts + + """ + + from pandas import Index, Series + + # compute counts on the data with no nans + data = self._data[~self._mask] + value_counts = Index(data).value_counts() + array = value_counts.values + + # TODO(extension) + # if we have allow Index to hold an ExtensionArray + # this is easier + index = value_counts.index.values.astype(bool).astype(object) + + # if we want nans, count the mask + if not dropna: + + # TODO(extension) + # appending to an Index *always* infers + # w/o passing the dtype + array = np.append(array, [self._mask.sum()]) + index = Index( + np.concatenate([index, np.array([np.nan], dtype=object)]), dtype=object + ) + + return Series(array, index=index) + + def _values_for_argsort(self) -> np.ndarray: + """ + Return values for sorting. + + Returns + ------- + ndarray + The transformed values should maintain the ordering between values + within the array. + + See Also + -------- + ExtensionArray.argsort + """ + data = self._data.copy() + data[self._mask] = -1 + return data + + @classmethod + def _create_logical_method(cls, op): + def logical_method(self, other): + + if isinstance(other, (ABCDataFrame, ABCSeries, ABCIndexClass)): + # Rely on pandas to unbox and dispatch to us. + return NotImplemented + + other = lib.item_from_zerodim(other) + mask = None + + if isinstance(other, BooleanArray): + other, mask = other._data, other._mask + elif is_list_like(other): + other = np.asarray(other, dtype="bool") + if other.ndim > 1: + raise NotImplementedError( + "can only perform ops with 1-d structures" + ) + if len(self) != len(other): + raise ValueError("Lengths must match to compare") + other, mask = coerce_to_array(other, copy=False) + + # numpy will show a DeprecationWarning on invalid elementwise + # comparisons, this will raise in the future + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", "elementwise", FutureWarning) + with np.errstate(all="ignore"): + result = op(self._data, other) + + # nans propagate + if mask is None: + mask = self._mask + else: + mask = self._mask | mask + + return BooleanArray(result, mask) + + name = "__{name}__".format(name=op.__name__) + return set_function_name(logical_method, name, cls) + + @classmethod + def _create_comparison_method(cls, op): + op_name = op.__name__ + + def cmp_method(self, other): + + if isinstance(other, (ABCDataFrame, ABCSeries, ABCIndexClass)): + # Rely on pandas to unbox and dispatch to us. + return NotImplemented + + other = lib.item_from_zerodim(other) + mask = None + + if isinstance(other, BooleanArray): + other, mask = other._data, other._mask + + elif is_list_like(other): + other = np.asarray(other) + if other.ndim > 1: + raise NotImplementedError( + "can only perform ops with 1-d structures" + ) + if len(self) != len(other): + raise ValueError("Lengths must match to compare") + + # numpy will show a DeprecationWarning on invalid elementwise + # comparisons, this will raise in the future + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", "elementwise", FutureWarning) + with np.errstate(all="ignore"): + result = op(self._data, other) + + # nans propagate + if mask is None: + mask = self._mask + else: + mask = self._mask | mask + + result[mask] = op_name == "ne" + return BooleanArray(result, np.zeros(len(result), dtype=bool), copy=False) + + name = "__{name}__".format(name=op.__name__) + return set_function_name(cmp_method, name, cls) + + def _reduce(self, name, skipna=True, **kwargs): + data = self._data + mask = self._mask + + # coerce to a nan-aware float if needed + if mask.any(): + data = self._data.astype("float64") + data[mask] = self._na_value + + op = getattr(nanops, "nan" + name) + result = op(data, axis=0, skipna=skipna, mask=mask, **kwargs) + + # if we have a boolean op, don't coerce + if name in ["any", "all"]: + pass + + # if we have numeric op that would result in an int, coerce to int if possible + elif name in ["sum", "prod"] and notna(result): + int_result = np.int64(result) + if int_result == result: + result = int_result + + elif name in ["min", "max"] and notna(result): + result = np.bool_(result) + + return result + + def _maybe_mask_result(self, result, mask, other, op_name): + """ + Parameters + ---------- + result : array-like + mask : array-like bool + other : scalar or array-like + op_name : str + """ + # if we have a float operand we are by-definition + # a float result + # or our op is a divide + if (is_float_dtype(other) or is_float(other)) or ( + op_name in ["rtruediv", "truediv"] + ): + result[mask] = np.nan + return result + + if is_bool_dtype(result): + return BooleanArray(result, mask, copy=False) + + elif is_integer_dtype(result): + from pandas.core.arrays import IntegerArray + + return IntegerArray(result, mask, copy=False) + else: + result[mask] = np.nan + return result + + @classmethod + def _create_arithmetic_method(cls, op): + op_name = op.__name__ + + def boolean_arithmetic_method(self, other): + + if isinstance(other, (ABCDataFrame, ABCSeries, ABCIndexClass)): + # Rely on pandas to unbox and dispatch to us. + return NotImplemented + + other = lib.item_from_zerodim(other) + mask = None + + if isinstance(other, BooleanArray): + other, mask = other._data, other._mask + + elif is_list_like(other): + other = np.asarray(other) + if other.ndim > 1: + raise NotImplementedError( + "can only perform ops with 1-d structures" + ) + if len(self) != len(other): + raise ValueError("Lengths must match") + + # nans propagate + if mask is None: + mask = self._mask + else: + mask = self._mask | mask + + with np.errstate(all="ignore"): + result = op(self._data, other) + + # divmod returns a tuple + if op_name == "divmod": + div, mod = result + return ( + self._maybe_mask_result(div, mask, other, "floordiv"), + self._maybe_mask_result(mod, mask, other, "mod"), + ) + + return self._maybe_mask_result(result, mask, other, op_name) + + name = "__{name}__".format(name=op_name) + return set_function_name(boolean_arithmetic_method, name, cls) + + +BooleanArray._add_logical_ops() +BooleanArray._add_comparison_ops() +BooleanArray._add_arithmetic_ops() diff --git a/pandas/tests/arrays/test_boolean.py b/pandas/tests/arrays/test_boolean.py new file mode 100644 index 0000000000000..5cfc7c3837875 --- /dev/null +++ b/pandas/tests/arrays/test_boolean.py @@ -0,0 +1,509 @@ +import operator + +import numpy as np +import pytest + +import pandas.util._test_decorators as td + +import pandas as pd +from pandas.arrays import BooleanArray +from pandas.core.arrays.boolean import coerce_to_array +from pandas.tests.extension.base import BaseOpsUtil +import pandas.util.testing as tm + + +def make_data(): + return [True, False] * 4 + [np.nan] + [True, False] * 44 + [np.nan] + [True, False] + + +@pytest.fixture +def dtype(): + return pd.BooleanDtype() + + +@pytest.fixture +def data(dtype): + return pd.array(make_data(), dtype=dtype) + + +def test_boolean_array_constructor(): + values = np.array([True, False, True, False], dtype="bool") + mask = np.array([False, False, False, True], dtype="bool") + + result = BooleanArray(values, mask) + expected = pd.array([True, False, True, None], dtype="boolean") + tm.assert_extension_array_equal(result, expected) + + with pytest.raises(TypeError, match="values should be boolean numpy array"): + BooleanArray(values.tolist(), mask) + + with pytest.raises(TypeError, match="mask should be boolean numpy array"): + BooleanArray(values, mask.tolist()) + + with pytest.raises(TypeError, match="values should be boolean numpy array"): + BooleanArray(values.astype(int), mask) + + with pytest.raises(TypeError, match="mask should be boolean numpy array"): + BooleanArray(values, None) + + with pytest.raises(ValueError, match="values must be a 1D array"): + BooleanArray(values.reshape(1, -1), mask) + + with pytest.raises(ValueError, match="mask must be a 1D array"): + BooleanArray(values, mask.reshape(1, -1)) + + +def test_boolean_array_constructor_copy(): + values = np.array([True, False, True, False], dtype="bool") + mask = np.array([False, False, False, True], dtype="bool") + + result = BooleanArray(values, mask) + assert result._data is values + assert result._mask is mask + + result = BooleanArray(values, mask, copy=True) + assert result._data is not values + assert result._mask is not mask + + +def test_to_boolean_array(): + expected = BooleanArray( + np.array([True, False, True]), np.array([False, False, False]) + ) + + result = pd.array([True, False, True], dtype="boolean") + tm.assert_extension_array_equal(result, expected) + result = pd.array(np.array([True, False, True]), dtype="boolean") + tm.assert_extension_array_equal(result, expected) + result = pd.array(np.array([True, False, True], dtype=object), dtype="boolean") + tm.assert_extension_array_equal(result, expected) + + # with missing values + expected = BooleanArray( + np.array([True, False, True]), np.array([False, False, True]) + ) + + result = pd.array([True, False, None], dtype="boolean") + tm.assert_extension_array_equal(result, expected) + result = pd.array(np.array([True, False, None], dtype=object), dtype="boolean") + tm.assert_extension_array_equal(result, expected) + + +def test_to_boolean_array_all_none(): + expected = BooleanArray(np.array([True, True, True]), np.array([True, True, True])) + + result = pd.array([None, None, None], dtype="boolean") + tm.assert_extension_array_equal(result, expected) + result = pd.array(np.array([None, None, None], dtype=object), dtype="boolean") + tm.assert_extension_array_equal(result, expected) + + +@pytest.mark.parametrize( + "a, b", + [ + ([True, None], [True, np.nan]), + ([None], [np.nan]), + ([None, np.nan], [np.nan, np.nan]), + ([np.nan, np.nan], [np.nan, np.nan]), + ], +) +def test_to_boolean_array_none_is_nan(a, b): + result = pd.array(a, dtype="boolean") + expected = pd.array(b, dtype="boolean") + tm.assert_extension_array_equal(result, expected) + + +@pytest.mark.parametrize( + "values", + [ + ["foo", "bar"], + ["1", "2"], + # "foo", + [1, 2], + [1.0, 2.0], + pd.date_range("20130101", periods=2), + np.array(["foo"]), + [np.nan, {"a": 1}], + ], +) +def test_to_boolean_array_error(values): + # error in converting existing arrays to BooleanArray + with pytest.raises(TypeError): + pd.array(values, dtype="boolean") + + +def test_to_boolean_array_integer_like(): + # integers of 0's and 1's + result = pd.array([1, 0, 1, 0], dtype="boolean") + expected = pd.array([True, False, True, False], dtype="boolean") + tm.assert_extension_array_equal(result, expected) + + result = pd.array(np.array([1, 0, 1, 0]), dtype="boolean") + tm.assert_extension_array_equal(result, expected) + + result = pd.array(np.array([1.0, 0.0, 1.0, 0.0]), dtype="boolean") + tm.assert_extension_array_equal(result, expected) + + # with missing values + result = pd.array([1, 0, 1, None], dtype="boolean") + expected = pd.array([True, False, True, None], dtype="boolean") + tm.assert_extension_array_equal(result, expected) + + result = pd.array(np.array([1.0, 0.0, 1.0, np.nan]), dtype="boolean") + tm.assert_extension_array_equal(result, expected) + + +def test_coerce_to_array(): + # TODO this is currently not public API + values = np.array([True, False, True, False], dtype="bool") + mask = np.array([False, False, False, True], dtype="bool") + result = BooleanArray(*coerce_to_array(values, mask=mask)) + expected = BooleanArray(values, mask) + tm.assert_extension_array_equal(result, expected) + assert result._data is values + assert result._mask is mask + result = BooleanArray(*coerce_to_array(values, mask=mask, copy=True)) + expected = BooleanArray(values, mask) + tm.assert_extension_array_equal(result, expected) + assert result._data is not values + assert result._mask is not mask + + # mixed missing from values and mask + values = [True, False, None, False] + mask = np.array([False, False, False, True], dtype="bool") + result = BooleanArray(*coerce_to_array(values, mask=mask)) + expected = BooleanArray( + np.array([True, False, True, True]), np.array([False, False, True, True]) + ) + tm.assert_extension_array_equal(result, expected) + result = BooleanArray(*coerce_to_array(np.array(values, dtype=object), mask=mask)) + tm.assert_extension_array_equal(result, expected) + result = BooleanArray(*coerce_to_array(values, mask=mask.tolist())) + tm.assert_extension_array_equal(result, expected) + + # raise errors for wrong dimension + values = np.array([True, False, True, False], dtype="bool") + mask = np.array([False, False, False, True], dtype="bool") + + with pytest.raises(ValueError, match="values must be a 1D list-like"): + coerce_to_array(values.reshape(1, -1)) + + with pytest.raises(ValueError, match="mask must be a 1D list-like"): + coerce_to_array(values, mask=mask.reshape(1, -1)) + + +def test_coerce_to_array_from_boolean_array(): + # passing BooleanArray to coerce_to_array + values = np.array([True, False, True, False], dtype="bool") + mask = np.array([False, False, False, True], dtype="bool") + arr = BooleanArray(values, mask) + result = BooleanArray(*coerce_to_array(arr)) + tm.assert_extension_array_equal(result, arr) + # no copy + assert result._data is arr._data + assert result._mask is arr._mask + + result = BooleanArray(*coerce_to_array(arr), copy=True) + tm.assert_extension_array_equal(result, arr) + assert result._data is not arr._data + assert result._mask is not arr._mask + + with pytest.raises(ValueError, match="cannot pass mask for BooleanArray input"): + coerce_to_array(arr, mask=mask) + + +def test_coerce_to_numpy_array(): + # with missing values -> object dtype + arr = pd.array([True, False, None], dtype="boolean") + result = np.array(arr) + expected = np.array([True, False, None], dtype="object") + tm.assert_numpy_array_equal(result, expected) + + # also with no missing values -> object dtype + arr = pd.array([True, False, True], dtype="boolean") + result = np.array(arr) + expected = np.array([True, False, True], dtype="object") + tm.assert_numpy_array_equal(result, expected) + + # force bool dtype + result = np.array(arr, dtype="bool") + expected = np.array([True, False, True], dtype="bool") + tm.assert_numpy_array_equal(result, expected) + # with missing values will raise error + arr = pd.array([True, False, None], dtype="boolean") + with pytest.raises(ValueError): + np.array(arr, dtype="bool") + + +def test_astype(): + # with missing values + arr = pd.array([True, False, None], dtype="boolean") + msg = "cannot convert float NaN to" + + with pytest.raises(ValueError, match=msg): + arr.astype("int64") + + with pytest.raises(ValueError, match=msg): + arr.astype("bool") + + result = arr.astype("float64") + expected = np.array([1, 0, np.nan], dtype="float64") + tm.assert_numpy_array_equal(result, expected) + + # no missing values + arr = pd.array([True, False, True], dtype="boolean") + result = arr.astype("int64") + expected = np.array([1, 0, 1], dtype="int64") + tm.assert_numpy_array_equal(result, expected) + + result = arr.astype("bool") + expected = np.array([True, False, True], dtype="bool") + tm.assert_numpy_array_equal(result, expected) + + +def test_astype_to_boolean_array(): + # astype to BooleanArray + arr = pd.array([True, False, None], dtype="boolean") + + result = arr.astype("boolean") + tm.assert_extension_array_equal(result, arr) + result = arr.astype(pd.BooleanDtype()) + tm.assert_extension_array_equal(result, arr) + + +def test_astype_to_integer_array(): + # astype to IntegerArray + arr = pd.array([True, False, None], dtype="boolean") + + result = arr.astype("Int64") + expected = pd.array([1, 0, None], dtype="Int64") + tm.assert_extension_array_equal(result, expected) + + +@pytest.mark.parametrize( + "ufunc", [np.add, np.logical_or, np.logical_and, np.logical_xor] +) +def test_ufuncs_binary(ufunc): + # two BooleanArrays + a = pd.array([True, False, None], dtype="boolean") + result = ufunc(a, a) + expected = pd.array(ufunc(a._data, a._data), dtype="boolean") + expected[a._mask] = np.nan + tm.assert_extension_array_equal(result, expected) + + s = pd.Series(a) + result = ufunc(s, a) + expected = pd.Series(ufunc(a._data, a._data), dtype="boolean") + expected[a._mask] = np.nan + tm.assert_series_equal(result, expected) + + # Boolean with numpy array + arr = np.array([True, True, False]) + result = ufunc(a, arr) + expected = pd.array(ufunc(a._data, arr), dtype="boolean") + expected[a._mask] = np.nan + tm.assert_extension_array_equal(result, expected) + + result = ufunc(arr, a) + expected = pd.array(ufunc(arr, a._data), dtype="boolean") + expected[a._mask] = np.nan + tm.assert_extension_array_equal(result, expected) + + # BooleanArray with scalar + result = ufunc(a, True) + expected = pd.array(ufunc(a._data, True), dtype="boolean") + expected[a._mask] = np.nan + tm.assert_extension_array_equal(result, expected) + + result = ufunc(True, a) + expected = pd.array(ufunc(True, a._data), dtype="boolean") + expected[a._mask] = np.nan + tm.assert_extension_array_equal(result, expected) + + # not handled types + with pytest.raises(TypeError): + ufunc(a, "test") + + +@pytest.mark.parametrize("ufunc", [np.logical_not]) +def test_ufuncs_unary(ufunc): + a = pd.array([True, False, None], dtype="boolean") + result = ufunc(a) + expected = pd.array(ufunc(a._data), dtype="boolean") + expected[a._mask] = np.nan + tm.assert_extension_array_equal(result, expected) + + s = pd.Series(a) + result = ufunc(s) + expected = pd.Series(ufunc(a._data), dtype="boolean") + expected[a._mask] = np.nan + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize("values", [[True, False], [True, None]]) +def test_ufunc_reduce_raises(values): + a = pd.array(values, dtype="boolean") + with pytest.raises(NotImplementedError): + np.add.reduce(a) + + +class TestLogicalOps(BaseOpsUtil): + def get_op_from_name(self, op_name): + short_opname = op_name.strip("_") + short_opname = short_opname if "xor" in short_opname else short_opname + "_" + try: + op = getattr(operator, short_opname) + except AttributeError: + # Assume it is the reverse operator + rop = getattr(operator, short_opname[1:]) + op = lambda x, y: rop(y, x) + + return op + + def _compare_other(self, data, op_name, other): + op = self.get_op_from_name(op_name) + + # array + result = pd.Series(op(data, other)) + expected = pd.Series(op(data._data, other), dtype="boolean") + + # fill the nan locations + expected[data._mask] = np.nan + + tm.assert_series_equal(result, expected) + + # series + s = pd.Series(data) + result = op(s, other) + + expected = pd.Series(data._data) + expected = op(expected, other) + expected = pd.Series(expected, dtype="boolean") + + # fill the nan locations + expected[data._mask] = np.nan + + tm.assert_series_equal(result, expected) + + def test_scalar(self, data, all_logical_operators): + op_name = all_logical_operators + self._compare_other(data, op_name, True) + + def test_array(self, data, all_logical_operators): + op_name = all_logical_operators + other = pd.array([True] * len(data), dtype="boolean") + self._compare_other(data, op_name, other) + other = np.array([True] * len(data)) + self._compare_other(data, op_name, other) + other = pd.Series([True] * len(data), dtype="boolean") + self._compare_other(data, op_name, other) + + +class TestComparisonOps(BaseOpsUtil): + def _compare_other(self, data, op_name, other): + op = self.get_op_from_name(op_name) + + # array + result = pd.Series(op(data, other)) + expected = pd.Series(op(data._data, other), dtype="boolean") + + # fill the nan locations + expected[data._mask] = op_name == "__ne__" + + tm.assert_series_equal(result, expected) + + # series + s = pd.Series(data) + result = op(s, other) + + expected = pd.Series(data._data) + expected = op(expected, other) + expected = expected.astype("boolean") + + # fill the nan locations + expected[data._mask] = op_name == "__ne__" + + tm.assert_series_equal(result, expected) + + def test_compare_scalar(self, data, all_compare_operators): + op_name = all_compare_operators + self._compare_other(data, op_name, True) + + def test_compare_array(self, data, all_compare_operators): + op_name = all_compare_operators + other = pd.array([True] * len(data), dtype="boolean") + self._compare_other(data, op_name, other) + other = np.array([True] * len(data)) + self._compare_other(data, op_name, other) + other = pd.Series([True] * len(data)) + self._compare_other(data, op_name, other) + + +class TestArithmeticOps(BaseOpsUtil): + def test_error(self, data, all_arithmetic_operators): + # invalid ops + + op = all_arithmetic_operators + s = pd.Series(data) + ops = getattr(s, op) + opa = getattr(data, op) + + # invalid scalars + with pytest.raises(TypeError): + ops("foo") + with pytest.raises(TypeError): + ops(pd.Timestamp("20180101")) + + # invalid array-likes + if op not in ("__mul__", "__rmul__"): + # TODO(extension) numpy's mul with object array sees booleans as numbers + with pytest.raises(TypeError): + ops(pd.Series("foo", index=s.index)) + + # 2d + result = opa(pd.DataFrame({"A": s})) + assert result is NotImplemented + + with pytest.raises(NotImplementedError): + opa(np.arange(len(s)).reshape(-1, len(s))) + + +@pytest.mark.parametrize("dropna", [True, False]) +def test_reductions_return_types(dropna, data, all_numeric_reductions): + op = all_numeric_reductions + s = pd.Series(data) + if dropna: + s = s.dropna() + + if op in ("sum", "prod"): + assert isinstance(getattr(s, op)(), np.int64) + elif op in ("min", "max"): + assert isinstance(getattr(s, op)(), np.bool_) + else: + # "mean", "std", "var", "median", "kurt", "skew" + assert isinstance(getattr(s, op)(), np.float64) + + +# TODO when BooleanArray coerces to object dtype numpy array, need to do conversion +# manually in the indexing code +# def test_indexing_boolean_mask(): +# arr = pd.array([1, 2, 3, 4], dtype="Int64") +# mask = pd.array([True, False, True, False], dtype="boolean") +# result = arr[mask] +# expected = pd.array([1, 3], dtype="Int64") +# tm.assert_extension_array_equal(result, expected) + +# # missing values -> error +# mask = pd.array([True, False, True, None], dtype="boolean") +# with pytest.raises(IndexError): +# result = arr[mask] + + +@td.skip_if_no("pyarrow", min_version="0.15.0") +def test_arrow_array(data): + # protocol added in 0.15.0 + import pyarrow as pa + + arr = pa.array(data) + expected = pa.array(np.array(data, dtype=object), type=pa.bool_(), from_pandas=True) + assert arr.equals(expected) diff --git a/pandas/tests/extension/test_boolean.py b/pandas/tests/extension/test_boolean.py new file mode 100644 index 0000000000000..089dd798b2512 --- /dev/null +++ b/pandas/tests/extension/test_boolean.py @@ -0,0 +1,333 @@ +""" +This file contains a minimal set of tests for compliance with the extension +array interface test suite, and should contain no other tests. +The test suite for the full functionality of the array is located in +`pandas/tests/arrays/`. + +The tests in this file are inherited from the BaseExtensionTests, and only +minimal tweaks should be applied to get the tests passing (by overwriting a +parent method). + +Additional tests should either be added to one of the BaseExtensionTests +classes (if they are relevant for the extension interface for all dtypes), or +be added to the array-specific tests in `pandas/tests/arrays/`. + +""" +import numpy as np +import pytest + +from pandas.compat.numpy import _np_version_under1p14 + +import pandas as pd +from pandas.core.arrays.boolean import BooleanDtype +from pandas.tests.extension import base +import pandas.util.testing as tm + + +def make_data(): + return [True, False] * 4 + [np.nan] + [True, False] * 44 + [np.nan] + [True, False] + + +@pytest.fixture +def dtype(): + return BooleanDtype() + + +@pytest.fixture +def data(dtype): + return pd.array(make_data(), dtype=dtype) + + +@pytest.fixture +def data_for_twos(dtype): + return pd.array(np.ones(100), dtype=dtype) + + +@pytest.fixture +def data_missing(dtype): + return pd.array([np.nan, True], dtype=dtype) + + +@pytest.fixture +def data_for_sorting(dtype): + return pd.array([True, True, False], dtype=dtype) + + +@pytest.fixture +def data_missing_for_sorting(dtype): + return pd.array([True, np.nan, False], dtype=dtype) + + +@pytest.fixture +def na_cmp(): + # we are np.nan + return lambda x, y: np.isnan(x) and np.isnan(y) + + +@pytest.fixture +def na_value(): + return np.nan + + +@pytest.fixture +def data_for_grouping(dtype): + b = True + a = False + na = np.nan + return pd.array([b, b, na, na, a, a, b], dtype=dtype) + + +class TestDtype(base.BaseDtypeTests): + pass + + +class TestInterface(base.BaseInterfaceTests): + pass + + +class TestConstructors(base.BaseConstructorsTests): + pass + + +class TestGetitem(base.BaseGetitemTests): + pass + + +class TestSetitem(base.BaseSetitemTests): + pass + + +class TestMissing(base.BaseMissingTests): + pass + + +class TestArithmeticOps(base.BaseArithmeticOpsTests): + def check_opname(self, s, op_name, other, exc=None): + # overwriting to indicate ops don't raise an error + super().check_opname(s, op_name, other, exc=None) + + def _check_op(self, s, op, other, op_name, exc=NotImplementedError): + if exc is None: + if op_name in ("__sub__", "__rsub__"): + # subtraction for bools raises TypeError (but not yet in 1.13) + if _np_version_under1p14: + pytest.skip("__sub__ does not yet raise in numpy 1.13") + with pytest.raises(TypeError): + op(s, other) + + return + + result = op(s, other) + expected = s.combine(other, op) + + if op_name in ( + "__floordiv__", + "__rfloordiv__", + "__pow__", + "__rpow__", + "__mod__", + "__rmod__", + ): + # combine keeps boolean type + expected = expected.astype("Int8") + elif op_name in ("__truediv__", "__rtruediv__"): + # combine with bools does not generate the correct result + # (numpy behaviour for div is to regard the bools as numeric) + expected = s.astype(float).combine(other, op) + if op_name == "__rpow__": + # for rpow, combine does not propagate NaN + expected[result.isna()] = np.nan + self.assert_series_equal(result, expected) + else: + with pytest.raises(exc): + op(s, other) + + def _check_divmod_op(self, s, op, other, exc=None): + # override to not raise an error + super()._check_divmod_op(s, op, other, None) + + @pytest.mark.skip(reason="BooleanArray does not error on ops") + def test_error(self, data, all_arithmetic_operators): + # other specific errors tested in the boolean array specific tests + pass + + +class TestComparisonOps(base.BaseComparisonOpsTests): + def check_opname(self, s, op_name, other, exc=None): + # overwriting to indicate ops don't raise an error + super().check_opname(s, op_name, other, exc=None) + + def _compare_other(self, s, data, op_name, other): + self.check_opname(s, op_name, other) + + +class TestReshaping(base.BaseReshapingTests): + pass + + +class TestMethods(base.BaseMethodsTests): + @pytest.mark.parametrize("na_sentinel", [-1, -2]) + def test_factorize(self, data_for_grouping, na_sentinel): + # override because we only have 2 unique values + labels, uniques = pd.factorize(data_for_grouping, na_sentinel=na_sentinel) + expected_labels = np.array( + [0, 0, na_sentinel, na_sentinel, 1, 1, 0], dtype=np.intp + ) + expected_uniques = data_for_grouping.take([0, 4]) + + tm.assert_numpy_array_equal(labels, expected_labels) + self.assert_extension_array_equal(uniques, expected_uniques) + + def test_combine_le(self, data_repeated): + # override because expected needs to be boolean instead of bool dtype + orig_data1, orig_data2 = data_repeated(2) + s1 = pd.Series(orig_data1) + s2 = pd.Series(orig_data2) + result = s1.combine(s2, lambda x1, x2: x1 <= x2) + expected = pd.Series( + [a <= b for (a, b) in zip(list(orig_data1), list(orig_data2))], + dtype="boolean", + ) + self.assert_series_equal(result, expected) + + val = s1.iloc[0] + result = s1.combine(val, lambda x1, x2: x1 <= x2) + expected = pd.Series([a <= val for a in list(orig_data1)], dtype="boolean") + self.assert_series_equal(result, expected) + + def test_searchsorted(self, data_for_sorting, as_series): + # override because we only have 2 unique values + data_for_sorting = pd.array([True, False], dtype="boolean") + b, a = data_for_sorting + arr = type(data_for_sorting)._from_sequence([a, b]) + + if as_series: + arr = pd.Series(arr) + assert arr.searchsorted(a) == 0 + assert arr.searchsorted(a, side="right") == 1 + + assert arr.searchsorted(b) == 1 + assert arr.searchsorted(b, side="right") == 2 + + result = arr.searchsorted(arr.take([0, 1])) + expected = np.array([0, 1], dtype=np.intp) + + tm.assert_numpy_array_equal(result, expected) + + # sorter + sorter = np.array([1, 0]) + assert data_for_sorting.searchsorted(a, sorter=sorter) == 0 + + +class TestCasting(base.BaseCastingTests): + pass + + +class TestGroupby(base.BaseGroupbyTests): + """ + Groupby-specific tests are overridden because boolean only has 2 + unique values, base tests uses 3 groups. + """ + + def test_grouping_grouper(self, data_for_grouping): + df = pd.DataFrame( + {"A": ["B", "B", None, None, "A", "A", "B"], "B": data_for_grouping} + ) + gr1 = df.groupby("A").grouper.groupings[0] + gr2 = df.groupby("B").grouper.groupings[0] + + tm.assert_numpy_array_equal(gr1.grouper, df.A.values) + tm.assert_extension_array_equal(gr2.grouper, data_for_grouping) + + @pytest.mark.parametrize("as_index", [True, False]) + def test_groupby_extension_agg(self, as_index, data_for_grouping): + df = pd.DataFrame({"A": [1, 1, 2, 2, 3, 3, 1], "B": data_for_grouping}) + result = df.groupby("B", as_index=as_index).A.mean() + _, index = pd.factorize(data_for_grouping, sort=True) + + index = pd.Index(index, name="B") + expected = pd.Series([3, 1], index=index, name="A") + if as_index: + self.assert_series_equal(result, expected) + else: + expected = expected.reset_index() + self.assert_frame_equal(result, expected) + + def test_groupby_extension_no_sort(self, data_for_grouping): + df = pd.DataFrame({"A": [1, 1, 2, 2, 3, 3, 1], "B": data_for_grouping}) + result = df.groupby("B", sort=False).A.mean() + _, index = pd.factorize(data_for_grouping, sort=False) + + index = pd.Index(index, name="B") + expected = pd.Series([1, 3], index=index, name="A") + self.assert_series_equal(result, expected) + + def test_groupby_extension_transform(self, data_for_grouping): + valid = data_for_grouping[~data_for_grouping.isna()] + df = pd.DataFrame({"A": [1, 1, 3, 3, 1], "B": valid}) + + result = df.groupby("B").A.transform(len) + expected = pd.Series([3, 3, 2, 2, 3], name="A") + + self.assert_series_equal(result, expected) + + def test_groupby_extension_apply(self, data_for_grouping, groupby_apply_op): + df = pd.DataFrame({"A": [1, 1, 2, 2, 3, 3, 1], "B": data_for_grouping}) + df.groupby("B").apply(groupby_apply_op) + df.groupby("B").A.apply(groupby_apply_op) + df.groupby("A").apply(groupby_apply_op) + df.groupby("A").B.apply(groupby_apply_op) + + def test_groupby_apply_identity(self, data_for_grouping): + df = pd.DataFrame({"A": [1, 1, 2, 2, 3, 3, 1], "B": data_for_grouping}) + result = df.groupby("A").B.apply(lambda x: x.array) + expected = pd.Series( + [ + df.B.iloc[[0, 1, 6]].array, + df.B.iloc[[2, 3]].array, + df.B.iloc[[4, 5]].array, + ], + index=pd.Index([1, 2, 3], name="A"), + name="B", + ) + self.assert_series_equal(result, expected) + + def test_in_numeric_groupby(self, data_for_grouping): + df = pd.DataFrame( + { + "A": [1, 1, 2, 2, 3, 3, 1], + "B": data_for_grouping, + "C": [1, 1, 1, 1, 1, 1, 1], + } + ) + result = df.groupby("A").sum().columns + + if data_for_grouping.dtype._is_numeric: + expected = pd.Index(["B", "C"]) + else: + expected = pd.Index(["C"]) + + tm.assert_index_equal(result, expected) + + +class TestNumericReduce(base.BaseNumericReduceTests): + def check_reduce(self, s, op_name, skipna): + result = getattr(s, op_name)(skipna=skipna) + expected = getattr(s.astype("float64"), op_name)(skipna=skipna) + # override parent function to cast to bool for min/max + if op_name in ("min", "max") and not pd.isna(expected): + expected = bool(expected) + tm.assert_almost_equal(result, expected) + + +class TestBooleanReduce(base.BaseBooleanReduceTests): + pass + + +class TestPrinting(base.BasePrintingTests): + pass + + +# TODO parsing not yet supported +# class TestParsing(base.BaseParsingTests): +# pass From 64fa6f34198276aed5a2758f096d5376038fa49a Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Mon, 2 Dec 2019 20:12:59 +0100 Subject: [PATCH 11/25] Fix broken xlrd test --- pandas/tests/io/excel/test_xlrd.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pandas/tests/io/excel/test_xlrd.py b/pandas/tests/io/excel/test_xlrd.py index e04dfc97d4968..cb3d27b66037d 100644 --- a/pandas/tests/io/excel/test_xlrd.py +++ b/pandas/tests/io/excel/test_xlrd.py @@ -10,9 +10,11 @@ @pytest.fixture(autouse=True) -def skip_ods_files(read_ext): +def skip_ods_and_xlsb_files(read_ext): if read_ext == ".ods": pytest.skip("Not valid for xlrd") + if read_ext == ".xlsb": + pytest.skip("Not valid for xlrd") def test_read_xlrd_book(read_ext, frame): From cb276e88ec94deb087a3bfd02c222b9bc365d6ab Mon Sep 17 00:00:00 2001 From: Rik-de-Kort <32839123+Rik-de-Kort@users.noreply.github.com> Date: Mon, 2 Dec 2019 23:54:40 +0100 Subject: [PATCH 12/25] get docs to build --- doc/source/user_guide/io.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/source/user_guide/io.rst b/doc/source/user_guide/io.rst index 449e173060c27..1740b7965412a 100644 --- a/doc/source/user_guide/io.rst +++ b/doc/source/user_guide/io.rst @@ -23,7 +23,7 @@ The pandas I/O API is a set of top level ``reader`` functions accessed like text;`JSON `__;:ref:`read_json`;:ref:`to_json` text;`HTML `__;:ref:`read_html`;:ref:`to_html` text; Local clipboard;:ref:`read_clipboard`;:ref:`to_clipboard` - binary;`MS Excel `__;:ref:`read_excel`;:ref:`to_excel` + ;`MS Excel `__;:ref:`read_excel`;:ref:`to_excel` binary;`OpenDocument `__;:ref:`read_excel`; binary;`HDF5 Format `__;:ref:`read_hdf`;:ref:`to_hdf` binary;`Feather Format `__;:ref:`read_feather`;:ref:`to_feather` @@ -3229,14 +3229,15 @@ OpenDocument spreadsheets match what can be done for `Excel files`_ using .. _io.xlsb: Binary Excel (.xlsb) files -------------------------- +-------------------------- .. versionadded:: 1.0.0 The :func:`~pandas.read_excel` method can also read binary Excel files using the ``pyxlsb`` module. The semantics and features for reading -binary Excel files match what can be done for `Excel files`_ using -``engine='pyxlsb'``. +binary Excel files mostly match what can be done for `Excel files`_ using +``engine='pyxlsb'``. ``pyxlsb`` does not recognize datetime types +in files and will return floats instead. .. code-block:: python From 4ebcb488b4d245735de17d0835e2ba7b53818eef Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Fri, 6 Dec 2019 09:27:24 +0100 Subject: [PATCH 13/25] Remove warning filter --- pandas/tests/io/excel/test_readers.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pandas/tests/io/excel/test_readers.py b/pandas/tests/io/excel/test_readers.py index 5f6f02664ac69..0152da5cb1b38 100644 --- a/pandas/tests/io/excel/test_readers.py +++ b/pandas/tests/io/excel/test_readers.py @@ -57,13 +57,7 @@ def ignore_xlrd_time_clock_warning(): pytest.mark.filterwarnings("ignore:.*(tree\\.iter|html argument)"), ], ), - pytest.param( - "pyxlsb", - marks=[ - td.skip_if_no("pyxlsb"), - pytest.mark.filterwarnings("ignore:.*(tree\\.iter|html argument)"), - ], - ), + pytest.param("pyxlsb", marks=td.skip_if_no("pyxlsb")), pytest.param("odf", marks=td.skip_if_no("odf")), ] ) From 00cc66b2930812de8ea3dcc2da9ad268ef4c54b4 Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Sat, 7 Dec 2019 10:41:43 +0100 Subject: [PATCH 14/25] extended description update --- pandas/io/excel/_base.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index 46cb67e4512e0..775479ea4a702 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -36,8 +36,9 @@ """ Read an Excel file into a pandas DataFrame. -Support both `xls` and `xlsx` file extensions from a local filesystem or URL. -Support an option to read a single sheet or a list of sheets. +Supports `xls`, `xlsx`, `xlsm`, `xlsb`, and `odf` file extensions +read from a local filesystem or URL. Supports an option to read +a single sheet or a list of sheets. Parameters ---------- From e85da03ae1618fe157027c89b23b00e9871647b6 Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Mon, 9 Dec 2019 12:27:47 +0100 Subject: [PATCH 15/25] Xlsb options instead of odf options --- pandas/core/config_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/config_init.py b/pandas/core/config_init.py index 0b13f3c3794c3..5bed28407076f 100644 --- a/pandas/core/config_init.py +++ b/pandas/core/config_init.py @@ -519,7 +519,7 @@ def use_inf_as_na_cb(key): cf.register_option( "reader", "auto", - reader_engine_doc.format(ext="xlsb", others=", ".join(_ods_options)), + reader_engine_doc.format(ext="xlsb", others=", ".join(_xlsb_options)), validator=str, ) From 2348c3b65eb5204180056eb8e5ec31d622c94c93 Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Wed, 11 Dec 2019 17:46:37 +0100 Subject: [PATCH 16/25] Add reference in whatsnew to docs --- doc/source/whatsnew/v1.0.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index 66dbb7d7d2941..a7b0be6313f40 100644 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -204,7 +204,7 @@ Other enhancements - Roundtripping DataFrames with nullable integer or string data types to parquet (:meth:`~DataFrame.to_parquet` / :func:`read_parquet`) using the `'pyarrow'` engine now preserve those data types with pyarrow >= 1.0.0 (:issue:`20612`). -- :func:`read_excel` now can read binary Excel (``.xlsb``) files by passing ``engine='pyxlsb'``. +- :func:`read_excel` now can read binary Excel (``.xlsb``) files by passing ``engine='pyxlsb'``. For more details and example usage, see the :ref:`Binary Excel files documentation ` Build Changes ^^^^^^^^^^^^^ From d02a5a563281d21e6a7ec30e99c6d9930645bab8 Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Wed, 11 Dec 2019 17:55:46 +0100 Subject: [PATCH 17/25] Make pyxlsb show up in install.rst and show_versions --- doc/source/getting_started/install.rst | 1 + pandas/compat/_optional.py | 1 + 2 files changed, 2 insertions(+) diff --git a/doc/source/getting_started/install.rst b/doc/source/getting_started/install.rst index 9f3ab22496ae7..41d56852a2e06 100644 --- a/doc/source/getting_started/install.rst +++ b/doc/source/getting_started/install.rst @@ -262,6 +262,7 @@ pyarrow 0.12.0 Parquet and feather reading / writi pymysql 0.7.11 MySQL engine for sqlalchemy pyreadstat SPSS files (.sav) reading pytables 3.4.2 HDF5 reading / writing +pyxlsb 1.0.5 Reading for xlsb files qtpy Clipboard I/O s3fs 0.3.0 Amazon S3 access xarray 0.8.2 pandas-like API for N-dimensional data diff --git a/pandas/compat/_optional.py b/pandas/compat/_optional.py index 0be201daea425..da40e7a23503d 100644 --- a/pandas/compat/_optional.py +++ b/pandas/compat/_optional.py @@ -19,6 +19,7 @@ "pyarrow": "0.12.0", "pytables": "3.4.2", "pytest": "5.0.1", + "pyxlsb": "1.0.5", "s3fs": "0.3.0", "scipy": "0.19.0", "sqlalchemy": "1.1.4", From c71e021ca6e6c9051d98db4d7c5525b44f9cf28e Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Sat, 14 Dec 2019 14:19:12 +0100 Subject: [PATCH 18/25] Add pyxlsb to ci builds --- ci/deps/azure-37-locale.yaml | 1 + ci/deps/azure-macos-36.yaml | 1 + ci/deps/azure-windows-37.yaml | 1 + ci/deps/travis-36-cov.yaml | 1 + 4 files changed, 4 insertions(+) diff --git a/ci/deps/azure-37-locale.yaml b/ci/deps/azure-37-locale.yaml index a10fa0904a451..d672a86ee6537 100644 --- a/ci/deps/azure-37-locale.yaml +++ b/ci/deps/azure-37-locale.yaml @@ -26,6 +26,7 @@ dependencies: - pytables - python-dateutil - pytz + - pyxlsb - s3fs - scipy - xarray diff --git a/ci/deps/azure-macos-36.yaml b/ci/deps/azure-macos-36.yaml index f393ed84ecf63..7859f9512c5aa 100644 --- a/ci/deps/azure-macos-36.yaml +++ b/ci/deps/azure-macos-36.yaml @@ -26,6 +26,7 @@ dependencies: - pytables - python-dateutil==2.6.1 - pytz + - pyxlsb - xarray - xlrd - xlsxwriter diff --git a/ci/deps/azure-windows-37.yaml b/ci/deps/azure-windows-37.yaml index 928896efd5fc4..6c4e9cf70bcbe 100644 --- a/ci/deps/azure-windows-37.yaml +++ b/ci/deps/azure-windows-37.yaml @@ -24,6 +24,7 @@ dependencies: - numexpr - numpy=1.14.* - openpyxl + - pyxlsb - pytables - python-dateutil - pytz diff --git a/ci/deps/travis-36-cov.yaml b/ci/deps/travis-36-cov.yaml index c1403f8eb8409..b468072f7b913 100644 --- a/ci/deps/travis-36-cov.yaml +++ b/ci/deps/travis-36-cov.yaml @@ -38,6 +38,7 @@ dependencies: - pytables - python-snappy - pytz + - pyxlsb - s3fs - scikit-learn - scipy From ae3f9eaa8a364506e41c65b3eca696002e7499cf Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Sat, 14 Dec 2019 14:20:09 +0100 Subject: [PATCH 19/25] environment.yml update --- environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment.yml b/environment.yml index 2b171d097a693..e436e76891915 100644 --- a/environment.yml +++ b/environment.yml @@ -83,6 +83,7 @@ dependencies: - pyqt>=5.9.2 # pandas.read_clipboard - pytables>=3.4.2 # pandas.read_hdf, DataFrame.to_hdf - python-snappy # required by pyarrow + - pyxlsb # pandas.read_excel - s3fs # pandas.read_csv... when using 's3://...' path - sqlalchemy # pandas.read_sql, DataFrame.to_sql - xarray # DataFrame.to_xarray From 7c9dccef67557306297f2a5e27ccf3f8248a0685 Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Thu, 19 Dec 2019 16:47:10 +0100 Subject: [PATCH 20/25] One update to environment.yml too many --- environment.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/environment.yml b/environment.yml index e436e76891915..2b171d097a693 100644 --- a/environment.yml +++ b/environment.yml @@ -83,7 +83,6 @@ dependencies: - pyqt>=5.9.2 # pandas.read_clipboard - pytables>=3.4.2 # pandas.read_hdf, DataFrame.to_hdf - python-snappy # required by pyarrow - - pyxlsb # pandas.read_excel - s3fs # pandas.read_csv... when using 's3://...' path - sqlalchemy # pandas.read_sql, DataFrame.to_sql - xarray # DataFrame.to_xarray From 4bd8400f7eb26460ffe32f71f737ce4c616b430e Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Mon, 23 Dec 2019 12:58:24 +0100 Subject: [PATCH 21/25] Trying to fix build --- ci/deps/azure-37-locale.yaml | 4 +++- ci/deps/azure-macos-36.yaml | 2 +- ci/deps/azure-windows-37.yaml | 4 +++- ci/deps/travis-36-cov.yaml | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ci/deps/azure-37-locale.yaml b/ci/deps/azure-37-locale.yaml index d672a86ee6537..9769371a3a1d7 100644 --- a/ci/deps/azure-37-locale.yaml +++ b/ci/deps/azure-37-locale.yaml @@ -26,7 +26,6 @@ dependencies: - pytables - python-dateutil - pytz - - pyxlsb - s3fs - scipy - xarray @@ -34,3 +33,6 @@ dependencies: - xlsxwriter - xlwt - pyarrow>=0.15 + - pip + - pip: + - pyxlsb diff --git a/ci/deps/azure-macos-36.yaml b/ci/deps/azure-macos-36.yaml index 7859f9512c5aa..a5bbe7a15b1a9 100644 --- a/ci/deps/azure-macos-36.yaml +++ b/ci/deps/azure-macos-36.yaml @@ -26,7 +26,6 @@ dependencies: - pytables - python-dateutil==2.6.1 - pytz - - pyxlsb - xarray - xlrd - xlsxwriter @@ -34,3 +33,4 @@ dependencies: - pip - pip: - pyreadstat + - pyxlsb diff --git a/ci/deps/azure-windows-37.yaml b/ci/deps/azure-windows-37.yaml index 6c4e9cf70bcbe..68d36b15c3f36 100644 --- a/ci/deps/azure-windows-37.yaml +++ b/ci/deps/azure-windows-37.yaml @@ -24,7 +24,6 @@ dependencies: - numexpr - numpy=1.14.* - openpyxl - - pyxlsb - pytables - python-dateutil - pytz @@ -35,3 +34,6 @@ dependencies: - xlsxwriter - xlwt - pyreadstat + - pip + - pip: + - pyxlsb diff --git a/ci/deps/travis-36-cov.yaml b/ci/deps/travis-36-cov.yaml index b468072f7b913..e780f5ba8c1e2 100644 --- a/ci/deps/travis-36-cov.yaml +++ b/ci/deps/travis-36-cov.yaml @@ -38,7 +38,6 @@ dependencies: - pytables - python-snappy - pytz - - pyxlsb - s3fs - scikit-learn - scipy @@ -54,3 +53,4 @@ dependencies: - coverage - pandas-datareader - python-dateutil + - pyxlsb From 024492a7c4a851fc00d91d47ed5a49e0999f9d82 Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Wed, 15 Jan 2020 08:56:58 +0100 Subject: [PATCH 22/25] Added issue number --- doc/source/whatsnew/v1.0.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index 434b0a46e9751..45fedde3b4b50 100755 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -216,7 +216,7 @@ Other enhancements - Roundtripping DataFrames with nullable integer, string and period data types to parquet (:meth:`~DataFrame.to_parquet` / :func:`read_parquet`) using the `'pyarrow'` engine now preserve those data types with pyarrow >= 1.0.0 (:issue:`20612`). -- :func:`read_excel` now can read binary Excel (``.xlsb``) files by passing ``engine='pyxlsb'``. For more details and example usage, see the :ref:`Binary Excel files documentation ` +- :func:`read_excel` now can read binary Excel (``.xlsb``) files by passing ``engine='pyxlsb'``. For more details and example usage, see the :ref:`Binary Excel files documentation `. Closes :issue:`8540`. - The ``partition_cols`` argument in :meth:`DataFrame.to_parquet` now accepts a string (:issue:`27117`) - :func:`pandas.read_json` now parses ``NaN``, ``Infinity`` and ``-Infinity`` (:issue:`12213`) - :func:`to_parquet` now appropriately handles the ``schema`` argument for user defined schemas in the pyarrow engine. (:issue:`30270`) From b424c8eb9c22c32a34b69c53bd7c32394d0c3828 Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Wed, 15 Jan 2020 09:03:40 +0100 Subject: [PATCH 23/25] Updated to use .rows(sparse=False) for future compat --- pandas/io/excel/_pyxlsb.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pandas/io/excel/_pyxlsb.py b/pandas/io/excel/_pyxlsb.py index 8249f5fdaef84..df6a38000452d 100644 --- a/pandas/io/excel/_pyxlsb.py +++ b/pandas/io/excel/_pyxlsb.py @@ -1,8 +1,7 @@ from typing import List -from pandas.compat._optional import import_optional_dependency - from pandas._typing import FilePathOrBuffer, Scalar +from pandas.compat._optional import import_optional_dependency from pandas.io.excel._base import _BaseExcelReader @@ -63,4 +62,7 @@ def _convert_cell(self, cell, convert_float: bool) -> Scalar: return cell.v def get_sheet_data(self, sheet, convert_float: bool) -> List[List[Scalar]]: - return [[self._convert_cell(c, convert_float) for c in r] for r in sheet] + return [ + [self._convert_cell(c, convert_float) for c in r] + for r in sheet.rows(sparse=False) + ] From dad4a5335bc82fc67e5bee8501cd8d63b0884ce7 Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Fri, 17 Jan 2020 12:00:23 +0100 Subject: [PATCH 24/25] xfails in test_readers.py --- pandas/tests/io/excel/test_readers.py | 44 +++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/pandas/tests/io/excel/test_readers.py b/pandas/tests/io/excel/test_readers.py index f3f0cd107f550..0acb1940b7b3a 100644 --- a/pandas/tests/io/excel/test_readers.py +++ b/pandas/tests/io/excel/test_readers.py @@ -146,6 +146,8 @@ def test_usecols_int(self, read_ext, df_ref): ) def test_usecols_list(self, read_ext, df_ref): + if pd.read_excel.keywords["engine"] == "pyxlsb": + pytest.xfail("Sheets containing datetimes not supported by pyxlsb") df_ref = df_ref.reindex(columns=["B", "C"]) df1 = pd.read_excel( @@ -160,6 +162,8 @@ def test_usecols_list(self, read_ext, df_ref): tm.assert_frame_equal(df2, df_ref, check_names=False) def test_usecols_str(self, read_ext, df_ref): + if pd.read_excel.keywords["engine"] == "pyxlsb": + pytest.xfail("Sheets containing datetimes not supported by pyxlsb") df1 = df_ref.reindex(columns=["A", "B", "C"]) df2 = pd.read_excel("test1" + read_ext, "Sheet1", index_col=0, usecols="A:D") @@ -192,6 +196,9 @@ def test_usecols_str(self, read_ext, df_ref): "usecols", [[0, 1, 3], [0, 3, 1], [1, 0, 3], [1, 3, 0], [3, 0, 1], [3, 1, 0]] ) def test_usecols_diff_positional_int_columns_order(self, read_ext, usecols, df_ref): + if pd.read_excel.keywords["engine"] == "pyxlsb": + pytest.xfail("Sheets containing datetimes not supported by pyxlsb") + expected = df_ref[["A", "C"]] result = pd.read_excel( "test1" + read_ext, "Sheet1", index_col=0, usecols=usecols @@ -207,11 +214,17 @@ def test_usecols_diff_positional_str_columns_order(self, read_ext, usecols, df_r tm.assert_frame_equal(result, expected, check_names=False) def test_read_excel_without_slicing(self, read_ext, df_ref): + if pd.read_excel.keywords["engine"] == "pyxlsb": + pytest.xfail("Sheets containing datetimes not supported by pyxlsb") + expected = df_ref result = pd.read_excel("test1" + read_ext, "Sheet1", index_col=0) tm.assert_frame_equal(result, expected, check_names=False) def test_usecols_excel_range_str(self, read_ext, df_ref): + if pd.read_excel.keywords["engine"] == "pyxlsb": + pytest.xfail("Sheets containing datetimes not supported by pyxlsb") + expected = df_ref[["C", "D"]] result = pd.read_excel( "test1" + read_ext, "Sheet1", index_col=0, usecols="A,D:E" @@ -278,12 +291,16 @@ def test_excel_stop_iterator(self, read_ext): tm.assert_frame_equal(parsed, expected) def test_excel_cell_error_na(self, read_ext): + if pd.read_excel.keywords["engine"] == "pyxlsb": + pytest.xfail("Sheets containing datetimes not supported by pyxlsb") parsed = pd.read_excel("test3" + read_ext, "Sheet1") expected = DataFrame([[np.nan]], columns=["Test"]) tm.assert_frame_equal(parsed, expected) def test_excel_table(self, read_ext, df_ref): + if pd.read_excel.keywords["engine"] == "pyxlsb": + pytest.xfail("Sheets containing datetimes not supported by pyxlsb") df1 = pd.read_excel("test1" + read_ext, "Sheet1", index_col=0) df2 = pd.read_excel("test1" + read_ext, "Sheet2", skiprows=[1], index_col=0) @@ -295,6 +312,8 @@ def test_excel_table(self, read_ext, df_ref): tm.assert_frame_equal(df3, df1.iloc[:-1]) def test_reader_special_dtypes(self, read_ext): + if pd.read_excel.keywords["engine"] == "pyxlsb": + pytest.xfail("Sheets containing datetimes not supported by pyxlsb") expected = DataFrame.from_dict( OrderedDict( @@ -492,6 +511,9 @@ def test_read_excel_blank_with_header(self, read_ext): def test_date_conversion_overflow(self, read_ext): # GH 10001 : pandas.ExcelFile ignore parse_dates=False + if pd.read_excel.keywords["engine"] == "pyxlsb": + pytest.xfail("Sheets containing datetimes not supported by pyxlsb") + expected = pd.DataFrame( [ [pd.Timestamp("2016-03-12"), "Marc Johnson"], @@ -508,9 +530,14 @@ def test_date_conversion_overflow(self, read_ext): tm.assert_frame_equal(result, expected) def test_sheet_name(self, read_ext, df_ref): + if pd.read_excel.keywords["engine"] == "pyxlsb": + pytest.xfail("Sheets containing datetimes not supported by pyxlsb") filename = "test1" sheet_name = "Sheet1" + if pd.read_excel.keywords["engine"] == "openpyxl": + pytest.xfail("Maybe not supported by openpyxl") + df1 = pd.read_excel( filename + read_ext, sheet_name=sheet_name, index_col=0 ) # doc @@ -603,6 +630,8 @@ def test_read_from_py_localpath(self, read_ext): tm.assert_frame_equal(expected, actual) def test_reader_seconds(self, read_ext): + if pd.read_excel.keywords["engine"] == "pyxlsb": + pytest.xfail("Sheets containing datetimes not supported by pyxlsb") # Test reading times with and without milliseconds. GH5945. expected = DataFrame.from_dict( @@ -631,6 +660,9 @@ def test_reader_seconds(self, read_ext): def test_read_excel_multiindex(self, read_ext): # see gh-4679 + if pd.read_excel.keywords["engine"] == "pyxlsb": + pytest.xfail("Sheets containing datetimes not supported by pyxlsb") + mi = MultiIndex.from_product([["foo", "bar"], ["a", "b"]]) mi_file = "testmultiindex" + read_ext @@ -790,6 +822,9 @@ def test_read_excel_chunksize(self, read_ext): def test_read_excel_skiprows_list(self, read_ext): # GH 4903 + if pd.read_excel.keywords["engine"] == "pyxlsb": + pytest.xfail("Sheets containing datetimes not supported by pyxlsb") + actual = pd.read_excel( "testskiprows" + read_ext, "skiprows_list", skiprows=[0, 2] ) @@ -930,6 +965,10 @@ def test_unexpected_kwargs_raises(self, read_ext, arg): pd.read_excel(excel, **kwarg) def test_excel_table_sheet_by_index(self, read_ext, df_ref): + # For some reason pd.read_excel has no attribute 'keywords' here. + # Skipping based on read_ext instead. + if read_ext == ".xlsb": + pytest.xfail("Sheets containing datetimes not supported by pyxlsb") with pd.ExcelFile("test1" + read_ext) as excel: df1 = pd.read_excel(excel, 0, index_col=0) @@ -953,6 +992,11 @@ def test_excel_table_sheet_by_index(self, read_ext, df_ref): tm.assert_frame_equal(df3, df1.iloc[:-1]) def test_sheet_name(self, read_ext, df_ref): + # For some reason pd.read_excel has no attribute 'keywords' here. + # Skipping based on read_ext instead. + if read_ext == ".xlsb": + pytest.xfail("Sheets containing datetimes not supported by pyxlsb") + filename = "test1" sheet_name = "Sheet1" From 9b6bc9a16ae9c6e304986cd4590a5ba664173bd6 Mon Sep 17 00:00:00 2001 From: Rik de Kort Date: Sat, 18 Jan 2020 11:29:24 +0100 Subject: [PATCH 25/25] xfail url loads --- pandas/tests/io/excel/test_readers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pandas/tests/io/excel/test_readers.py b/pandas/tests/io/excel/test_readers.py index 0acb1940b7b3a..f8ff3567b8b64 100644 --- a/pandas/tests/io/excel/test_readers.py +++ b/pandas/tests/io/excel/test_readers.py @@ -562,6 +562,10 @@ def test_bad_engine_raises(self, read_ext): @tm.network def test_read_from_http_url(self, read_ext): + if read_ext == ".xlsb": + pytest.xfail("xlsb files not present in master repo yet") + if pd.read_excel.keywords["engine"] == "pyxlsb": + pytest.xfail("Sheets containing datetimes not supported by pyxlsb") url = ( "https://raw.githubusercontent.com/pandas-dev/pandas/master/"