100% found this document useful (2 votes)
13 views

(Ebook) Python Programming for Data Analysis by José Unpingco ISBN 9783030689513, 3030689514 2024 Scribd Download

The document provides information about various Python programming ebooks available for download, including titles focused on data analysis, statistics, and machine learning. It highlights specific books by authors such as José Unpingco and Nelli Fabio, along with their ISBNs and links for access. Additionally, the document emphasizes the importance of Python programming knowledge for effective data analysis and offers resources for learning through practical examples.

Uploaded by

liebejevanhj
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (2 votes)
13 views

(Ebook) Python Programming for Data Analysis by José Unpingco ISBN 9783030689513, 3030689514 2024 Scribd Download

The document provides information about various Python programming ebooks available for download, including titles focused on data analysis, statistics, and machine learning. It highlights specific books by authors such as José Unpingco and Nelli Fabio, along with their ISBNs and links for access. Additionally, the document emphasizes the importance of Python programming knowledge for effective data analysis and offers resources for learning through practical examples.

Uploaded by

liebejevanhj
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 55

Download the Full Ebook and Access More Features - ebooknice.

com

(Ebook) Python Programming for Data Analysis by


José Unpingco ISBN 9783030689513, 3030689514

https://ebooknice.com/product/python-programming-for-data-
analysis-26149366

OR CLICK HERE

DOWLOAD EBOOK

Download more ebook instantly today at https://ebooknice.com


Instant digital products (PDF, ePub, MOBI) ready for you
Download now and discover formats that fit your needs...

Start reading on any device today!

(Ebook) Python Data Analytics: Data Analysis and Science Using Pandas, Matplotlib
and the Python Programming Language by Nelli Fabio ISBN 9781484209592, 1484209591

https://ebooknice.com/product/python-data-analytics-data-analysis-and-science-
using-pandas-matplotlib-and-the-python-programming-language-38169124

ebooknice.com

(Ebook) Python Data Analytics: Data Analysis and Science Using Pandas, Matplotlib
and the Python Programming Language by Nelli Fabio ISBN 9781484209592, 1484209591

https://ebooknice.com/product/python-data-analytics-data-analysis-and-science-
using-pandas-matplotlib-and-the-python-programming-language-38180776

ebooknice.com

(Ebook) Python Data Analytics: Data Analysis and Science Using Pandas, Matplotlib
and the Python Programming Language by Nelli Fabio ISBN 9781484209592, 1484209591

https://ebooknice.com/product/python-data-analytics-data-analysis-and-science-
using-pandas-matplotlib-and-the-python-programming-language-38180778

ebooknice.com

(Ebook) Python for Probability, Statistics, and Machine Learning by José Unpingco
ISBN 9783030185442, 3030185443

https://ebooknice.com/product/python-for-probability-statistics-and-machine-
learning-10421734

ebooknice.com
(Ebook) Data Analysis with Python: Introducing NumPy, Pandas, Matplotlib, and
Essential Elements of Python Programming by Rituraj Dixit ISBN 9789355510655,
9355510659

https://ebooknice.com/product/data-analysis-with-python-introducing-numpy-
pandas-matplotlib-and-essential-elements-of-python-programming-45347694

ebooknice.com

(Ebook) Python for Probability, Statistics, and Machine Learning, 2nd Edition by
José Unpingco ISBN 9783030185442, 3030185443

https://ebooknice.com/product/python-for-probability-statistics-and-machine-
learning-2nd-edition-43798232

ebooknice.com

(Ebook) Python for Probability, Statistics, and Machine Learning by José Unpingco
ISBN 9783319307152, 9783319307176, 3319307150, 3319307177

https://ebooknice.com/product/python-for-probability-statistics-and-machine-
learning-5484040

ebooknice.com

(Ebook) Applied Longitudinal Data Analysis for Epidemiology: A Practical Guide by


Jos W. R. Twisk ISBN 9781107030039, 110703003X

https://ebooknice.com/product/applied-longitudinal-data-analysis-for-
epidemiology-a-practical-guide-5294478

ebooknice.com

(Ebook) Python for Data Analysis by Wes McKinney ISBN 9781449319793, 1449319793

https://ebooknice.com/product/python-for-data-analysis-36170736

ebooknice.com
José Unpingco

Python
Programming
for Data
Analysis
Python Programming for Data Analysis
José Unpingco

Python Programming
for Data Analysis
José Unpingco
University of California
San Diego
CA, USA

ISBN 978-3-030-68951-3 ISBN 978-3-030-68952-0 (eBook)


https://doi.org/10.1007/978-3-030-68952-0

© The Editor(s) (if applicable) and The Author(s), under exclusive license to Springer Nature Switzerland
AG 2021
This work is subject to copyright. All rights are solely and exclusively licensed by the Publisher, whether
the whole or part of the material is concerned, specifically the rights of translation, reprinting, reuse
of illustrations, recitation, broadcasting, reproduction on microfilms or in any other physical way, and
transmission or information storage and retrieval, electronic adaptation, computer software, or by similar
or dissimilar methodology now known or hereafter developed.
The use of general descriptive names, registered names, trademarks, service marks, etc. in this publication
does not imply, even in the absence of a specific statement, that such names are exempt from the relevant
protective laws and regulations and therefore free for general use.
The publisher, the authors, and the editors are safe to assume that the advice and information in this book
are believed to be true and accurate at the date of publication. Neither the publisher nor the authors or
the editors give a warranty, expressed or implied, with respect to the material contained herein or for any
errors or omissions that may have been made. The publisher remains neutral with regard to jurisdictional
claims in published maps and institutional affiliations.

This Springer imprint is published by the registered company Springer Nature Switzerland AG
The registered company address is: Gewerbestrasse 11, 6330 Cham, Switzerland
To Irene, Nicholas, and Daniella, for all their
patient support.
Preface

This book grew out of notes for the ECE143 Programming for Data Analysis class
that I have been teaching at the University of California, San Diego, which is a
requirement for both graduate and undergraduate degrees in Machine Learning and
Data Science. The reader is assumed to have some basic programming knowledge
and experience using another language, such as Matlab or Java. The Python idioms
and methods we discuss here focus on data analysis, notwithstanding Python’s
usage across many other topics. Specifically, because raw data is typically a mess
and requires much work to prepare, this text focuses on specific Python language
features to facilitate such cleanup, as opposed to only focusing on particular Python
modules for this.
As with ECE143, here we discuss why things are the way they are in Python
instead of just that they are this way. I have found that providing this kind of
context helps students make better engineering design choices in their codes, which
is especially helpful for newcomers to both Python and data analysis. The text is
sprinkled with little tricks of the trade to make it easier to create readable and
maintainable code suitable for use in both production and development.
The text focuses on using the Python language itself effectively and then moves
on to key third-party modules. This approach improves effectiveness in different
environments, which may or may not have access to such third-party modules. The
Numpy numerical array module is covered in depth because it is the foundation
of all data science and machine learning in Python. We discuss the Numpy array
data structure in detail, especially its memory aspects. Next, we move on to Pandas
and develop its many features for effective and fluid data processing. Because data
visualization is key to data science and machine learning, third-party modules such
as Matplotlib are developed in depth, as well as web-based modules such as Bokeh,
Holoviews, Plotly, and Altair.
On the other hand, I would not recommend this book to someone with no
programming experience at all, but if you can do a little Python already and want to
improve by understanding how and why Python works the way it does, then this is
a good book for you.

vii
viii Preface

To get the most out of this book, open a Python interpreter and type-along with
the many code samples. I worked hard to ensure that all of the given code samples
work as advertised.

Acknowledgments I would like to acknowledge the help of Brian Granger and Fernando Perez,
two of the originators of the Jupyter Notebook, for all their great work, as well as the Python
community as a whole, for all their contributions that made this book possible. Hans Petter
Langtangen was the author of the Doconce [1] document preparation system that was used to
write this text. Thanks to Geoffrey Poore [2] for his work with PythonTeX and LATEX, both key
technologies used to produce this book.

San Diego, CA, USA José Unpingco


February, 2020

References

1. H.P. Langtangen, DocOnce markup language. https://github.com/hplgit/doconce


2. G.M. Poore, Pythontex: reproducible documents with latex, python, and more. Comput. Sci.
Discov. 8(1), 014010 (2015)
Contents

1 Basic Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1 Basic Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.1 Getting Started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.1.2 Reserved Keywords . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1.3 Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1.4 Complex Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.1.5 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.1.6 Loops and Conditionals. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.1.7 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.1.8 File Input/Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
1.1.9 Dealing with Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
1.1.10 Power Python Features to Master . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
1.1.11 Generators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
1.1.12 Decorators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
1.1.13 Iteration and Iterables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
1.1.14 Using Python Assertions to Pre-debug Code . . . . . . . . . . . . . . . 60
1.1.15 Stack Tracing with sys.settrace . . . . . . . . . . . . . . . . . . . . . . 61
1.1.16 Debugging Using IPython . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
1.1.17 Logging from Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
2 Object-Oriented Programming. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
2.1 Properties/Attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
2.2 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
2.3 Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
2.4 Class Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
2.5 Class Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
2.6 Static Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
2.7 Hashing Hides Parent Variables from Children . . . . . . . . . . . . . . . . . . . . . . 76
2.8 Delegating Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
2.9 Using super for Delegation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
2.10 Metaprogramming: Monkey Patching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

ix
x Contents

2.11 Abstract Base Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80


2.12 Descriptors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
2.13 Named Tuples and Data Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
2.14 Generic Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
2.15 Design Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
2.15.1 Template . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
2.15.2 Singleton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
2.15.3 Observer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
2.15.4 Adapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
3 Using Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
3.1 Standard Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
3.2 Writing and Using Your Own Modules. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
3.2.1 Using a Directory as a Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
3.3 Dynamic Importing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
3.4 Getting Modules from the Web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
3.5 Conda Package Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
4 Numpy. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
4.1 Dtypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
4.2 Multidimensional Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
4.3 Reshaping and Stacking Numpy Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
4.4 Duplicating Numpy Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
4.5 Slicing, Logical Array Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
4.6 Numpy Arrays and Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
4.7 Numpy Memory Data Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
4.8 Array Element-Wise Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
4.9 Universal Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
4.10 Numpy Data Input/Output. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
4.11 Linear Algebra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
4.12 Broadcasting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
4.13 Masked Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
4.14 Floating Point Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
4.15 Advanced Numpy dtypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
5 Pandas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
5.1 Using Series . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
5.2 Using DataFrame . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
5.3 Reindexing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
5.4 Deleting Items . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
5.5 Advanced Indexing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
5.6 Broadcasting and Data Alignment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
5.7 Categorical and Merging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
Contents xi

5.8 Memory Usage and dtypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143


5.9 Common Operations. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
5.10 Displaying DataFrames. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
5.11 Multi-index. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
5.12 Pipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
5.13 Data Files and Databases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
5.14 Customizing Pandas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
5.15 Rolling and Filling Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
6 Visualizing Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
6.1 Matplotlib . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
6.1.1 Setting Defaults . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
6.1.2 Legends . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
6.1.3 Subplots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
6.1.4 Spines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
6.1.5 Sharing Axes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
6.1.6 3D Surfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
6.1.7 Using Patch Primitives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
6.1.8 Patches in 3D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
6.1.9 Using Transformations. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
6.1.10 Annotating Plots with Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
6.1.11 Annotating Plots with Arrows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
6.1.12 Embedding Scalable and Non-scalable Subplots . . . . . . . . . . . 173
6.1.13 Animations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
6.1.14 Using Paths Directly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
6.1.15 Interacting with Plots Using Sliders . . . . . . . . . . . . . . . . . . . . . . . . 179
6.1.16 Colormaps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
6.1.17 Low-Level Control Using setp and getp . . . . . . . . . . . . . . . 183
6.1.18 Interacting with Matplotlib Figures . . . . . . . . . . . . . . . . . . . . . . . . . 183
6.1.19 Keyboard Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
6.1.20 Mouse Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
6.2 Seaborn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
6.2.1 Automatic Aggregation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
6.2.2 Multiple Plots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
6.2.3 Distribution Plots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
6.3 Bokeh. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
6.3.1 Using Bokeh Primitives. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
6.3.2 Bokeh Layouts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
6.3.3 Bokeh Widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
6.4 Altair . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
6.4.1 Detailing Altair. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
6.4.2 Aggregations and Transformations. . . . . . . . . . . . . . . . . . . . . . . . . . 217
6.4.3 Interactive Altair . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
6.5 Holoviews . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
6.5.1 Dataset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
xii Contents

6.5.2 Image Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232


6.5.3 Tabular Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
6.5.4 Customizing Interactivity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
6.5.5 Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
6.5.6 Pandas Integration with hvplot . . . . . . . . . . . . . . . . . . . . . . . . . . 237
6.5.7 Network Graphs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
6.5.8 Holoviz Panel for Dashboards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
6.6 Plotly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259

Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
Chapter 1
Basic Programming

1.1 Basic Language

Before we get into the details, it is a good idea to get a high-level orientation to
Python. This will improve your decision-making later regarding software develop-
ment for your own projects, especially as these get bigger and more complex. Python
grew out of a language called ABC, which was developed in the Netherlands in the
1980s as an alternative to BASIC to get scientists to utilize microcomputers, which
were new at the time. The important impulse was to make non-specialist scientists
able to productively utilize these new computers. Indeed, this pragmatic approach
continues to this day in Python which is a direct descendent of the ABC language.
There is a saying in Python—come for the language, stay for the community.
Python is an open source project that is community driven so there is no corporate
business entity making top-down decisions for the language. It would seem that such
an approach would lead to chaos but Python has benefited over many years from
the patient and pragmatic leadership of Guido van Rossum, the originator of the
language. Nowadays, there is a separate governance committee that has taken over
this role since Guido’s retirement. The open design of the language and the quality
of the source code has made it possible for Python to enjoy many contributions from
talented developers all over the world over many years, as embodied by the richness
of the standard library. Python is also legendary for having a welcoming community
for newcomers so it is easy to find help online for getting started with Python.
The pragmatism of the language and the generosity of the community have long
made Python a great way to develop web applications. Before the advent of data
science and machine learning, upwards of 80% of the Python community were web
developers. In the last five years (at the time of this writing), the balance is tilted to
an almost even split between web developers and data scientists. This is the reason
you find a lot of web protocols and technology in the standard library.
Python is an interpreted language as opposed to a compiled language like C
or FORTRAN. Although both cases start with a source code file, the compiler

© The Editor(s) (if applicable) and The Author(s), under exclusive license 1
to Springer Nature Switzerland AG 2021
J. Unpingco, Python Programming for Data Analysis,
https://doi.org/10.1007/978-3-030-68952-0_1
2 1 Basic Programming

examines the source code end-to-end and produces an executable that is linked to
system-specific library files. Once the executable is created, there is no longer any
need for the compiler. You can just run the executable on the system. On the other
hand, an interpreted language like Python you must always have a running Python
process to execute the code. This is because the Python process is an abstraction on
the platform it is running on and thus must interpret the instructions in the source
code to execute them on the platform. As the intermediary between the source
code on the platform, the Python interpreter is responsible for the platform specific
issues. The advantage of this is that source code can be run on different platforms as
long as there is a working Python interpreter on each platform. This makes Python
source codes portable between platforms because the platform specific details are
handled by the respective Python interpreters. Portability between platforms was
a key advantage of Python, especially in the early days. Going back to compiled
languages, because the platform specific details are embedded in the executable, the
executable is tied to a specific platform and to those specific libraries that have been
linked into the executable. This makes these codes are less portable than Python,
but because the compiler is able to link to the specific platform it has the option
to exploit platform- specific level optimizations and libraries. Furthermore, the
compiler can study the source code file and apply compiler-level optimizations that
accelerate the resulting executable. In broad strokes, those are the key differences
between interpreted and compiled languages. We will later see that there are many
compromises between these two extremes for accelerating Python codes.
It is sometimes said that Python is slow as compared to compiled languages,
and pursuant to the differences we discussed above, that may be expected, but it
is really a question of where the clock starts. If you start the clock to account for
developer time, not just code runtime, then Python is clearly faster, just because
the development iteration cycle does not require a tedious compile and link step.
Furthermore, Python is just simpler to use than compiled languages because so
many tricky elements like memory management are handled automatically. Pythons
quick turnaround time is a major advantage for product development, which requires
rapid iteration. On the other hand, codes that are compute-limited and must run
on specialized hardware are not good use-cases for Python. These include solving
systems of parallel differential equations simulating large-scale fluid mechanics or
other large-scale physics calculations. Note that Python is used in such settings but
mainly for staging these computations or postprocessing the resulting data.

1.1.1 Getting Started

Your primary interface to the Python interpreter is the commandline. You can type
in python in your terminal you should see something like the following,
Python 3.7.3 (default, Mar 27 2019, 22:11:17)
[GCC 7.3.0] :: Anaconda, Inc. on linux
1.1 Basic Language 3

Type "help", "copyright", "credits" or "license" for more


→ information.
>>>

There is a lot of useful information including the version of Python and its
provenance. This matters because sometimes the Python interpreter is compiled to
allow fast access to certain preferred modules (i.e., math module). We will discuss
this more when we talk about Python modules.

1.1.2 Reserved Keywords

Although Python will not stop you, do not use these as variable or function names.
and del from not while
as elif global or with
assert else if pass yield
break except import print
class exec in raise
continue finally is return
def for lambda try

nor these neither


abs all any ascii bin bool breakpoint bytearray bytes callable
chr classmethod compile complex copyright credits delattr
dict dir display divmod enumerate eval exec filter float
format frozenset getattr globals hasattr hash help hex id
input int isinstance issubclass iter len list locals map max
memoryview min next object oct open ord pow print property
range repr reversed round set setattr slice sorted
staticmethod str sum super tuple type vars zip

For example, a common mistake is to assign sum=10, not realizing that now the
sum() Python function is no longer available.

1.1.3 Numbers

Python has common-sense number-handling capabilities. The comment character is


the hash (#) symbol.
>>> 2+2
4
>>> 2+2 # and a comment on the same line as code
4
>>> (50-5*6)/4
5.0

Note that division in Python 2 is integer-division and in Python 3 it is floating


point division with the // symbol providing integer-division. Python is dynamically
4 1 Basic Programming

typed, so we can do the following assignments without declaring the types of


width and height.
>>> width = 20
>>> height = 5*9
>>> width * height
900
>>> x = y = z = 0 # assign x, y and z to zero
>>> x
0
>>> y
0
>>> z
0
>>> 3 * 3.75 / 1.5
7.5
>>> 7.0 / 2 # float numerator
3.5
>>> 7/2
3.5
>>> 7 // 2 # double slash gives integer division
3

It is best to think of assignments as labeling values in memory. Thus, width is a


label for the number 20. This will make more sense later.1 Since Python 3.8, the
walrus assignment operator allows the assignment itself to have the value of the
assignee, as in the following,
>>> print(x:=10)
10
>>> print(x)
10

The operator has many other subtle uses and was introduced to improve readability
in certain situations. You can also cast among the numeric types in a common-sense
way:
>>> int(1.33333)
1
>>> float(1)
1.0
>>> type(1)
<class 'int'>
>>> type(float(1))
<class 'float'>

Importantly, Python integers are of arbitrary length and can handle extremely large
integers. This is because they are stored as a list of digits internally. This means that
they are slower to manipulate than Numpy integers which have fixed bit-lengths,
provided that the integer can fit into allocated memory.

1 Notehttp://www.pythontutor.com is a great resource for exploring how variables are assigned in


Python.
1.1 Basic Language 5

Programming Tip: IPython


The default Python interpreter is useful and you should be familiar with it
but the IPython interpretera is a useful extension that provides features like
sophisticated tab-completion and many other conveniences that make it easier
to work with Python in the terminal.
a
See http://github.com/ipython/ipython for the latest on how to get started with IPython.

1.1.4 Complex Numbers

Python has rudimentary support for complex numbers.


>>> 1j * 1J
(-1+0j)
>>> 1j * complex(0,1)
(-1+0j)
>>> 3+1j*3
(3+3j)
>>> (3+1j)*3
(9+3j)
>>> (1+2j)/(1+1j)
(1.5+0.5j)
>>> a=1.5+0.5j
>>> a.real # the dot notation gets an attribute
1.5
>>> a.imag
0.5
>>> a=3.0+4.0j
>>> float(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't convert complex to float
>>> a.real
3.0
>>> a.imag
4.0
>>> abs(a) # sqrt(a.real**2 + a.imag**2)
5.0
>>> tax = 12.5 / 100
>>> price = 100.50
>>> price * tax
12.5625
>>> price + _
113.0625
>>> # the underscore character is the last evaluated result
>>> round(_, 2) # the underscore character is the last evaluated
→ result
113.06
6 1 Basic Programming

We typically use Numpy for complex numbers, though.

1.1.5 Strings

String-handling is very well developed and highly optimized in Python. We just


cover the main points here. First, single or double quotes define a string and there is
no precedence relationship between single and double quotes.
>>> 'spam eggs'
'spam eggs'
>>> 'doesn\'t' # backslash defends single quote
"doesn't"
>>> "doesn't"
"doesn't"
>>> '"Yes," he said.'
'"Yes," he said.'
>>> "\"Yes,\" he said."
'"Yes," he said.'
>>> '"Isn\'t," she said.'
'"Isn\'t," she said.'

Python strings have C-style escape characters for newlinewsnewlines, tabs, etc.
String literals defined this way are by default encoded using UTF-8 instead of
ASCII, as in Python 2. The triple single (’) or triple double quote (") denotes a
block with embedded newlines or other quotes. This is particularly important for
function documentation docstrings that we will discussed later.
>>> print( '''Usage: thingy [OPTIONS]
... and more lines
... here and
... here
... ''')
Usage: thingy [OPTIONS]
and more lines
here and
here

Strings can be controlled with a character before the single or double quote. For
example, see the comments (#) below for each step,
>>> # the 'r' makes this a 'raw' string
>>> hello = r"This long string contains newline characters \n, as
→ in C"
>>> print(hello)
This long string contains newline characters \n, as in C
>>> # otherwise, you get the newline \n acting as such
>>> hello = "This long string contains newline characters \n, as
→ in C"
>>> print(hello)
This long string contains newline characters
, as in C
1.1 Basic Language 7

>>> u'this a unicode string μ ±' # the 'u' makes it a unicode


→ string for Python2
'this a unicode string μ ±'
>>> 'this a unicode string μ ±' # no 'u' in Python3 is still
→ unicode string
'this a unicode string μ ±'
>>> u'this a unicode string \xb5 \xb1' # using hex-codes
'this a unicode string μ ±'

Note that a f-string evaluates (i.e., interpolates) the Python variables in the
current scope,
>>> x = 10
>>> s = f'{x}'
>>> type(s)
<class 'str'>
>>> s
'10'

Beware that an f-string is not resolved until run-time because it has to resolve
the embedded variables. This means that you cannot use f-strings as docstrings.
Importantly, Python strings are immutable which means that once a string is created,
it cannot be changed in-place. For example,
>>> x = 'strings are immutable '
>>> x[0] = 'S' # not allowed!
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

This means you have to create new strings to make this kind of change.
Strings vs Bytes In Python 3, the default string encoding for string literals is UTF-
8. The main thing to keep in mind is that bytes and strings are now distinct objects,
as opposed to both deriving from basestring in Python 2. For example, given
the following unicode string,
>>> x='Ø'
>>> isinstance(x,str) # True
True
>>> isinstance(x,bytes) # False
False
>>> x.encode('utf8') # convert to bytes with encode
b'\xc3\x98'

Note the distinction between bytes and strings. We can convert bytes to strings using
decode,
>>> x=b'\xc3\x98'
>>> isinstance(x,bytes) # True
True
>>> isinstance(x,str) # False
False
>>> x.decode('utf8')
'Ø'
8 1 Basic Programming

An important consequence is that you cannot append strings and bytes as in the
following: u"hello"+b"goodbye". This used to work fine in Python 2 because
bytes would automatically be decoded using ASCII, but this no longer works in
Python 3. To get this behavior, you have to explicitly decode/encode. For
example,
>>> x=b'\xc3\x98'
>>> isinstance(x,bytes) # True
True
>>> y='banana'
>>> isinstance(y,str) # True
True
>>> x+y.encode()
b'\xc3\x98banana'
>>> x.decode()+y
'Øbanana'

Slicing Strings Python is a zero-indexed language (like C). The colon (:) character
denotes .
>>> word = 'Help' + 'A'
>>> word
'HelpA'
>>> '<' + word*5 + '>'
'<HelpAHelpAHelpAHelpAHelpA>'
>>> word[4]
'A'
>>> word[0:2]
'He'
>>> word[2:4]
'lp'
>>> word[-1] # The last character
'A'
>>> word[-2] # The last-but-one character
'p'
>>> word[-2:] # The last two characters
'pA'
>>> word[:-2] # Everything except the last two characters
'Hel'

String Operations Some basic numerical operations work with strings.


>>> 'hey '+'you' # concatenate with plus operator
'hey you'
>>> 'hey '*3 # integer multiplication duplicates strings
'hey hey hey '
>>> ('hey ' 'you') # using parentheses without separating comma
'hey you'

Python has a built-in and very powerful regular expression module (re) for string
manipulations. String substitution creates new strings.
>>> x = 'This is a string'
>>> x.replace('string','newstring')
'This is a newstring'
>>> x # x hasn't changed
'This is a string'
1.1 Basic Language 9

Formatting Strings There are so many ways to format strings in Python, but here
is the simplest that follows C-language sprintf conventions in conjunction with
the modulo operator %.
>>> 'this is a decimal number %d'%(10)
'this is a decimal number 10'
>>> 'this is a float %3.2f'%(10.33)
'this is a float 10.33'
>>> x = 1.03
>>> 'this is a variable %e' % (x) # exponential format
'this is a variable 1.030000e+00'

Alternatively, you can just join them using +,


>>> x = 10
>>> 'The value of x = '+str(x)
'The value of x = 10'

You can format using dictionaries as in the following,


>>> data = {'x': 10, 'y':20.3}
>>> 'The value of x = %(x)d and y = %(y)f'%(data)
'The value of x = 10 and y = 20.300000'

You can use the format method on the string,


>>> x = 10
>>> y = 20
>>> 'x = {0}, y = {1}'.format(x,y)
'x = 10, y = 20'

The advantage of format is you can reuse the placeholders as in the following,
>>> 'x = {0},{1},{0}; y = {1}'.format(x,y)
'x = 10,20,10; y = 20'

And also the f-string method we discussed above.

Programming Tip: Python 2 Strings


In Python 2, the default string encoding was 7-bit ASCII. There was no
distinction between bytes and strings. For example, you could read from a
binary-encoded JPG file as in the following,
with open('hour_1a.jpg','r') as f:
x = f.read()

This works fine in Python 2 but throws a UnicodeDecodeError error in


Python 3. To fix this in Python 3, you have to read using the rb binary mode
instead of just the r file mode.

Basic Data Structures Python provides many powerful data structures. The two
most powerful and fundamental are the list and dictionary. Data structures and
10 1 Basic Programming

algorithms go hand-in-hand. If you do not understand data structures, then you


cannot effectively write algorithms and vice versa. Fundamentally, data structures
provide guarantees to the programmer that will be fulfilled if the data structures are
used in the agreed-upon manner. These guarantees are known as the invariants for
the data structure.
Lists The list is an order-preserving general container that implements the
sequence data structure. The invariant for the list is that indexing a non-empty list
will always give you the next valid element in order. Indeed, the list is Python’s
primary ordered data structure. This means that if you have a problem where order
is important, then you should be thinking about the list data structure. This will
make sense with following examples.
>>> mix = [3,'tree',5.678,[8,4,2]] # can contain sublists
>>> mix
[3, 'tree', 5.678, [8, 4, 2]]
>>> mix[0] # zero-indexed Python
3
>>> mix[1] # indexing individual elements
'tree'
>>> mix[-2] # indexing from the right, same as strings
5.678
>>> mix[3] # get sublist
[8, 4, 2]
>>> mix[3][1] # last element is sublist
4
>>> mix[0] = 666 # lists are mutable
>>> mix
[666, 'tree', 5.678, [8, 4, 2]]
>>> submix = mix[0:3] # creating sublist
>>> submix
[666, 'tree', 5.678]
>>> switch = mix[3] + submix # append two lists with plus
>>> switch
[8, 4, 2, 666, 'tree', 5.678]
>>> len(switch) # length of list is built-in function
6
>>> resize=[6.45,'SOFIA',3,8.2E6,15,14]
>>> len(resize)
6
>>> resize[1:4] = [55] # assign slices
>>> resize
[6.45, 55, 15, 14]
>>> len(resize) # shrink a sublist
4
>>> resize[3]=['all','for','one']
>>> resize
[6.45, 55, 15, ['all', 'for', 'one']]
>>> len(resize)
4
>>> resize[4]=2.87 # cannot append this way!
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list assignment index out of range
>>> temp = resize[:3]
1.1 Basic Language 11

>>> resize = resize + [2.87] # add to list


>>> resize
[6.45, 55, 15, ['all', 'for', 'one'], 2.87]
>>> len(resize)
5
>>> del resize[3] # delete item
>>> resize
[6.45, 55, 15, 2.87]
>>> len(resize) # shorter now
4
>>> del resize[1:3] # delete a sublist
>>> resize
[6.45, 2.87]
>>> len(resize) # shorter now
2

Programming Tip: Sorting Lists


The built-in function sorted sorts lists,
>>> sorted([1,9,8,2])
[1, 2, 8, 9]

Lists can also be sorted in-place using the sort() list method,
>>> x = [1,9,8,2]
>>> x.sort()
>>> x
[1, 2, 8, 9]

Both of these use the powerful Timsort algorithm. Later, we will see more
variations and uses for these sorting functions.

Now that we have a feel for how to index and use lists, let us talk about the invariant
that it provides: as long as you index a list within its bounds, it provides the next
ordered element of the list. For example,
>>> x = ['a',10,'c']
>>> x[1] # return 10
10
>>> x.remove(10)
>>> x[1] # next element
'c'

Notice that the list data structure filled in the gap after removing 10. This is extra
work that the list data structure did for you without explicitly programming. Also,
list elements are accessible via integer indices and integers have a natural ordering
and thus so does the list. The work of maintaining the invariant does not come for
free, however. Consider the following,
>>> x = [1,3,'a']
>>> x.insert(0,10) # insert at beginning
12 1 Basic Programming

>>> x
[10, 1, 3, 'a']

Seem harmless? Sure, for small lists, but not so for large lists. This is because
to maintain the invariant the list has to scoot (i.e., memory copy) the remaining
elements over to the right to accommodate the new element added at the beginning.
Over a large list with millions of elements and in a loop, this can take a substantial
amount of time. This is why the default append() and pop() list methods work
at the tail end of the list, where there is no need to scoot items to the right.
Tuples Tuples are another general purpose sequential container in Python, very
similar to lists, but these are immutable. Tuples are delimited by commas (parenthe-
ses are grouping symbols). Here are some examples,
>>> a = 1,2,3 # no parenthesis needed!
>>> type(a)
<class 'tuple'>
>>> pets=('dog','cat','bird')
>>> pets[0]
'dog'
>>> pets + pets # addition
('dog', 'cat', 'bird', 'dog', 'cat', 'bird')
>>> pets*3
('dog', 'cat', 'bird', 'dog', 'cat', 'bird', 'dog', 'cat', 'bird')
>>> pets[0]='rat' # assignment not work!
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

It may seem redundant to have tuples which behave in terms of their indexing like
lists, but the key difference is that tuples are immutable, as the last line above shows.
The key advantage of immutability is that it comes with less overhead for Python
memory management. In this sense, they are lighter weight and provide stability for
codes that pass tuples around. Later, we will see this for function signatures, which
is where the major advantages of tuples arise.

Programming Tip: Understanding List Memory


Python’s id function shows an integer corresponding to the internal reference
for a given variable. Earlier, we suggested considering variable assignment as
labeling because internally Python works with a variable’s id, not its variable
name/label.
>>> x = y = z = 10.1100
>>> id(x) # different labels for same id
140271927806352
>>> id(y)
140271927806352
>>> id(z)
140271927806352

(continued)
1.1 Basic Language 13

This is more consequential for mutable data structures like lists. Consider the
following,
>>> x = y = [1,3,4]
>>> x[0] = 'a'
>>> x
['a', 3, 4]
>>> y
['a', 3, 4]
>>> id(x),id(y)
(140271930505344, 140271930505344)

Because x and y are merely two labels for the same underlying list, changes to
one of the labels affects both lists. Python is inherently stingy about allocating
new memory so if you want to have two different lists with the same content,
you can force a copy as in the following,
>>> x = [1,3,4]
>>> y = x[:] # force copy
>>> id(x),id(y) # different ids now!
(140271930004160, 140271929640448)
>>> x[1] = 99
>>> x
[1, 99, 4]
>>> y # retains original data
[1, 3, 4]

Tuple Unpacking Tuples unpack assignments in order as follows:,


>>> a,b,c = 1,2,3
>>> a
1
>>> b
2
>>> c
3

Python 3 can unpack tuples in chunks using the * operator,


>>> x,y,*z = 1,2,3,4,5
>>> x
1
>>> y
2
>>> z
[3, 4, 5]

Note how the z variable collected the remaining items in the assignment. You can
also change the order of the chunking,
>>> x,*y,z = 1,2,3,4,5
>>> x
1
14 1 Basic Programming

>>> y
[2, 3, 4]
>>> z
5

This unpacking is sometimes called de-structuring, or splat, in case you read this
term elsewhere.
Dictionaries Python dictionaries are central to Python because many other ele-
ments (e.g., functions, classes) are built around them. Effectively programming
Python means using dictionaries effectively. Dictionaries are general containers that
implement the mapping data structure, which is sometimes called a hash table or
associative array. Dictionaries require a key/value pair, which maps the key to the
value.
>>> x = {'key': 'value'}

The curly braces and the colon make the dictionary. To retrieve the value from the
x dictionary, you must index it with the key as shown,
>>> x['key']
'value'

Let us get started with some basic syntax.


>>> x={'play':'Shakespeare','actor':'Wayne','direct':'Kubrick',
... 'author':'Hemmingway','bio':'Watson'}

>>> len(x) # number of key/value pairs


5
>>> x['pres']='F.D.R.' # assignment to key 'pres'
>>> x
{'play': 'Shakespeare', 'actor': 'Wayne', 'direct': 'Kubrick',
→ 'author': 'Hemmingway', 'bio': 'Watson', 'pres': 'F.D.R.'}
>>> x['bio']='Darwin' # reassignment for 'bio' key
>>> x
{'play': 'Shakespeare', 'actor': 'Wayne', 'direct': 'Kubrick',
→ 'author': 'Hemmingway', 'bio': 'Darwin', 'pres': 'F.D.R.'}
>>> del x['actor'] # delete key/value pair
>>> x
{'play': 'Shakespeare', 'direct': 'Kubrick', 'author':
→ 'Hemmingway', 'bio': 'Darwin', 'pres': 'F.D.R.'}

Dictionaries can also be created with the dict built-in function,


>>> # another way of creating a dict
>>> x=dict(key='value',
... another_key=1.333,
... more_keys=[1,3,4,'one'])
>>> x
{'key': 'value', 'another_key': 1.333, 'more_keys': [1, 3, 4,
→ 'one']}
>>> x={(1,3):'value'} # any immutable type can be a valid key
>>> x
{(1, 3): 'value'}
>>> x[(1,3)]='immutables can be keys'
1.1 Basic Language 15

As generalized containers, dictionaries can contain other dictionaries or lists or other


Python types.

Programming Tip: Unions of Dictionaries


What if you want to create a union of dictionaries in one- line?
>>> d1 = {'a':1, 'b':2, 'c':3}
>>> d2 = {'A':1, 'B':2, 'C':3}
>>> dict(d1,**d2) # combo of d1 and d2
{'a': 1, 'b': 2, 'c': 3, 'A': 1, 'B': 2, 'C': 3}
>>> {**d1,**d2} # without dict function
{'a': 1, 'b': 2, 'c': 3, 'A': 1, 'B': 2, 'C': 3}

Pretty slick.

The invariant that the dictionary provides is that as long as you provide a valid key,
then it will always retrieve the corresponding value; or, in the case of assignment,
store the value reliably. Recall that lists are ordered data structures in the sense that
when elements are indexed, the next element can be found by a relative offset from
the prior one. This means that these elements are laid out contiguously in memory.
Dictionaries do not have this property because they will put values wherever they
can find memory, contiguous or not. This is because dictionaries do not rely upon
relative offsets for indexing, they rely instead on a hash function. Consider the
following,
>>> x = {0: 'zero', 1: 'one'}
>>> y = ['zero','one']
>>> x[1] # dictionary
'one'
>>> y[1] # list
'one'

Indexing both variables looks notationally the same in both cases, but the process
is different. When given a key, the dictionary computes a hash function and the
stores the value at a memory location based upon the hash function. What is a hash
function? A hash function takes an input and is designed to return, with very high
probability, a value that is unique to the key. In particular, this means that two keys
cannot have the same hash, or, equivalently, cannot store different values in the
same memory location. Here are two keys which are almost identical, but have very
different hashes.
>>> hash('12345')
3973217705519425393
>>> hash('12346')
3824627720283660249

All this is with respect to probability, though. Because memory is finite, it could
happen that the hash function produces values that are the same. This is known
as a hash collision and Python implements fallback algorithms to handle this
16 1 Basic Programming

case. Nonetheless, as memory becomes scarce, especially on a small platform, the


struggle to find suitable blocks of memory can be noticeable if your code uses many
large dictionaries.
As we discussed before, inserting/removing elements from the middle of a list
causes extra memory movement as the list maintains its invariant but this does
not happen for dictionaries. This means that elements can be added or removed
without any extra memory overhead beyond the cost of computing the hash function
(i.e., constant-time lookup). Thus, dictionaries are ideal for codes that do not need
ordering. Note that since Python 3.6+, dictionaries are ordered in the sense of the
order in which items were inserted to the dictionary. In Python 2.7, this was known
as collections.OrderedDict but has since become the default in Python
3.6+.
Now that we have a good idea of how dictionaries work, consider the inputs to
the hash function: the keys. We have mainly used integers and strings for keys, but
any immutable type can also be used, such as a tuple,
>>> x= {(1,3):10, (3,4,5,8):10}

However, if you try to use a mutable type as a key,


>>> a = [1,2]
>>> x[a]= 10
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Let us think about why this happens. Remember that the hash function guarantees
that when given a key it will always be able to retrieve the value. Suppose that it
were possible use mutable keys in dictionaries. In the above code, we would have
hash(a) -> 132334, as an example, and let us suppose the 10 value is inserted
in that memory slot. Later in the code, we could change the contents of a as in
a[0]=3. Now, because the hash function is guaranteed to produce different outputs
for different inputs, the hash function output would be different from 132334 and
thus the dictionary could not retrieve the corresponding value, which would violate
its invariant. Thus we have arrived at a contradiction that explains why dictionary
keys must be immutable.
Sets Python provides mathematical sets and corresponding operations with the
set() data structure, which are basically dictionaries without values.
>>> set([1,2,11,1]) # union-izes elements
{1, 2, 11}
>>> set([1,2,3]) & set([2,3,4]) # bitwise intersection
{2, 3}
>>> set([1,2,3]) and set([2,3,4])
{2, 3, 4}
>>> set([1,2,3]) ^ set([2,3,4]) # bitwise exclusive OR
{1, 4}
>>> set([1,2,3]) | set([2,3,4]) # OR
{1, 2, 3, 4}
>>> set([ [1,2,3],[2,3,4] ]) # no sets of lists
1.1 Basic Language 17

(without more work)


Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Note that since Python 3.6+, keys can be used as set objects, as in the following,
>>> d = dict(one=1,two=2)
>>> {'one','two'} & d.keys() # intersection
{'one', 'two'}
>>> {'one','three'} | d.keys() # union
{'one', 'two', 'three'}

This also works for dictionary items if the values are hashable,
>>> d = dict(one='ball',two='play')
>>> {'ball','play'} | d.items()
{'ball', 'play', ('one', 'ball'), ('two', 'play')}

Once you create a set, you can add individual elements or remove them as follows:,
>>> s = {'one',1,3,'10'}
>>> s.add('11')
>>> s
{1, 3, 'one', '11', '10'}
>>> s.discard(3)
>>> s
{1, 'one', '11', '10'}

Remember sets are not ordered and you cannot directly index any of the constituent
items. Also, the subset() method is for a proper subset not a partial subset. For
example,
>>> a = {1,3,4,5}
>>> b = {1,3,4,5,6}
>>> a.issubset(b)
True
>>> a.add(1000)
>>> a.issubset(b)
False

And likewise for issuperset. Sets are optimal for fast lookups in Python, as in
the following,
>>> a = {1,3,4,5,6}
>>> 1 in a
True
>>> 11 in a
False

which works really fast, even for large sets.

1.1.6 Loops and Conditionals

There are two primary looping constructs in Python: the for loop and the while
loop. The syntax of the for loop is straightforward:
18 1 Basic Programming

>>> for i in range(3):


... print(i)
...
0
1
2

Note the colon character at the end. This is your hint that the next line should be
indented. In Python, blocks are denoted by whitespace indentation (four spaces
is recommended) which makes the code more readable anyway. The for loop
iterates over items that provided by the iterator, which is the range(3) list
in the above example. Python abstracts the idea of iterable out of the looping
construction so that some Python objects are iterable all by themselves and are just
waiting for an iteration provider like a for or while loop to get them going.
Interestingly, Python has an else clause, which is used to determine whether or
not the loop exited with a break3 or not.
>>> for i in [1,2,3]:
... if i>20:
... break # won't happen
... else:
... print('no break here!')
...
no break here!

The else block is only executed when the loop terminates without breaking.
The while loop has a similar straightforward construct:
>>> i = 0
>>> while i < 3:
... i += 1
... print(i)
...
1
2
3

This also has a corresponding optional else block. Again, note that the presence
of the colon character hints at indenting the following line. The while loop will
continue until the boolean expression (i.e., i<10) evaluates False. Let us consider
boolean and membership in Python.
Logic and Membership Python is a truthy language in the sense that things are
true except for the following:
• None
• False
• zero of any numeric type, for example, 0, 0L, 0.0, 0j.
• any empty sequence, for example, ”, (), [].

3 There is also a continue statement which will jump to the top of the for or while loop.
Another Random Scribd Document
with Unrelated Content
“Ja, ja, zeker,” antwoordde Lodewijk.
“Welnu,” hernam Wolfangh, “ik verliet mijn dorp, niets mede
nemende dan geld, wraakzucht en eenen dolk. Lang heb ik den
schaker opgezocht zonder hem te vinden, doch hoe meer ik wachten
moest, hoe meer ik zwoer mijne Helena te wreken. Eens bij Brussel
langs de Senne wandelende, klonken mij een tiental stemmen te
gelijk in ’t oor. Midden onder zoovele personen ontwaarde ik mijnen
aartsvijand. Mijn bloed liep ongestuimig door mijne aderen, en het
hart klopte mij zoodanig, dat ik bijna roerloos werd, — doch de lust
tot wraak stijfde mijnen arm, want mijn dolk ging tot aan het gevest in
des schakers borst. Ik sprong in de Senne en zwom in een oogenblik
tot aan den anderen oever. Daar bleef ik lachend en van vreugd
opgetogen staan. Twee pistoolschoten waren op mij gelost
geworden, doch door geen werd ik getroffen. Met wellust zag ik mijn
slachtoffer huilend ten gronde zinken, en na ik van zijnen dood
verzekerd was, vloog ik als een pijl tusschen de boomen, om mij aan
mijne nieuwe vervolgers te onttrekken. Ook werd ik als een wild
zwijn uit alle plaatsten verjaagd, geen mensch dorst mij herbergen.
— Mijn vader werd om mij vervolgd en door druk en smart ten grave
geleid. Nergens kon ik eene schuilplaats vinden, en wanneer de
naam van Wolfangh op eene markt werd uitgesproken, dan ging de
schreeuw: “houd aan, sla dood!” uit alle monden, alsof ik een dolle
hond ware geweest. Zeg mij, jonkheer, wat kon ik dan zonder geld
doen? Na lang zwerven heb ik hier, in dit bosch, een schuilplaats
gevonden. De nood maakte mij tot eenen dief, en de vervolging der
gerechtsdienaren, alhoewel wettig, tot eenen moordenaar. Ik heb
geleden en wroeging gehad over mijn misdadig leven, doch het lot
was sterker dan mijn moed. — Gij, jonkheer, hebt mij het middel
aangewezen om mij te redden. Daarom, ontvang nogmaals mijne
dankzegging. Nu komt het beeld van Helena mij weder levendig voor
de oogen. Ik hoop, dat hare gebeden mij genade zullen doen vinden
bij God.”
Hij zweeg een oogenblik en, de ontroering bemerkende, welke zijn
verhaal in Lodewijk had verwekt, stond hij van zijnen zetel op en
sprak:
“Nu, jonkheer, ik wil u niet langer hier houden. Zeg aan Godmaert,
dat ik zijne voorwaarden aanneem en eenen spie in de stad zal
zenden, om op het oogenblik van den toestand der zaken verwittigd
te zijn. Dat hij alles overlegge, en op den dag der omwenteling zullen
Wolfangh en zijne makkers dáár zijn.”
“Vóórdat ik u verlate, Wolfangh, moet ik nog eenige woorden tot u
spreken. Een uwer mannen uitte daareven het inzicht om de kerken
te berooven.”
“Dit denken zij, ja; maar vrees deswege niets: mijn wil is eene
stalen wet, die niemand onder hen zou durven verbreken.”
“Dit alleen wilde ik van u niet verzoeken; ik wilde u daarenboven
eene gelegenheid aanwijzen om eene poging te doen, die
ongetwijfeld kan medewerken tot het verdienen der vergiffenis van
uw zondig leven.”
“Zeg, zeg, jonkheer, ik ben bereid om uwen raad te volgen.”
“Gij weet misschien niet, Wolfangh, dat de grootste hoop
dergenen, die zich Geuzen noemen, ketters en afgevallen zijn, en
dat zij den dag der omwenteling afwachten, om al de teekens van
onzen godsdienst te niet te doen?”
“Ik weet het, jonker.”
“Gij weet het! Welnu, help mij en eenigen mijner vrienden in het
beschermen onzer kerken. Het zal moeilijk zijn, ik voorzie het wel;
maar misschien gelukken wij in onze pogingen.”
Op Wolfanghs aangezicht glom eene uitdrukking van genoegen;
hij vatte Lodewijks hand en sprak met nadruk:
“Ga, jonkheer, gij zult over Wolfangh tevreden zijn, ik hoop het.
Vaarwel, tot wederzien!”
Een gewapende roover werd aan Lodewijk tot leidsman gegeven.
Deze bracht hem en zijn paard ongehinderd het bosch uit. Nu steeg
hij op en vervolgde den weg, die hem bij de hut zou brengen. Zeker
zou hij gedoold hebben; doch de dankbare landman, voor zijnen
weldoener bekommerd, had een groot licht aan zijn venster
ontstoken. Deze baak bracht den jongeling eindelijk bij de eenzame
woning. De deur werd met haast geopend, en blij gejuich kwam hem
hartelijk verwelkomen. Het avondmaal stond op de tafel bereid. Na
eenige woorden over des jongelings gelukkige terugkomst
gesproken te hebben, bad hem de landman, zich bij de tafel neder te
zetten. Dit deed de hongerige Lodewijk en at, tot der inwoners
vreugde, met meer smaak dan of hij in een paleis ware genoodigd
geweest.
“Jonkheer,” sprak de landman, “het is bij middernacht, en, daar de
baan met schelmen overdekt is, bid ik u, in mijne arme woning te
vernachten.”
En hij wees hem eene bedstede, waarover zuivere lakens
gespreid waren.
Lodewijk bedacht, dat hij, laat als het was, Godmaert toch vóór
den dag geene kennis zijner zending geven kon. Daarom besloot hij
des heibewoners voorstel aan te nemen.
Na hun allen eene goede rust gewenscht te hebben, legde hij zich,
vermoeid en welgemoed, op het bed neder.
V
Dos schreefse met eene zwarte roede een rinck op d’aerde; daer
moest ick midden in staen: voorts brochtse oock allerhande
vreemde dinghen in den rinck, en na dat ick mercken kon, soo
docht my dat het waren Leeuwenklaeuwen, Hondenoogen,
Wolfstanden, Boksbloed, Ezelsooren, enz.
Duyfken en Willemynken.

Denzelfden dag dat Lodewijk zijne reis begonnen had, en, door
liefdedroomen langs de baan vergezeld, aan zijne Geertruid dacht,
ging er in Godmaerts woning iets om, waarvan de kennis aan onzen
jonker menigen bitteren traan moest kosten.
Het was juist twee uren na middag, Godmaert en zijne dochter
zaten rustig over onverschillige onderwerpen te spreken.
“Maar, vader,” viel Geertruid hem in de rede, “die Spanjaard heeft
immers de macht niet om zijne bedreigingen ten uitvoer te brengen?”
“Welke bedreigingen, mijne dochter?” vroeg de Geus verbaasd.
“De dienstboden hebben mij gezegd, dat Valdès u de gevangenis
heeft toegezeid. Wist gij dat niet?”
“De gevangenis!” zuchtte hij, “de gevangenis!”
Een sombere angst betrok zijn gelaat. Hij vatte de hand zijner
dochter en drukte ze met liefde.
“Geertruid,” hernam hij mistroostig, “ja, de Spanjaard is een rijk en
arglistig man. Zeg mij, zoo het lot u eenmaal van uwen ouden vader
scheidde, zoudt gij dan dien hartbrekenden slag wel kunnen
verdragen?”
“Maar, vader,” antwoordde het treurig meisje, “gij hebt immers
geene misdaad begaan? De rechters zouden uwe onschuld weldra
erkennen en niet lijden, dat men u in de gevangenis brengen zou?”
“Kind,” sprak Godmaert, “gij kent de wereld niet. Voorwaar, ik zeg
u, het is zeer mogelijk, dat men mij uit mijne woning rukke. Alhoewel
wij aan een loffelijk werk arbeiden, zijn wij niettemin strafbaar
volgens de bestaande wetten, want wij werpen ons op tegen den
heerschenden koning. Voor mij vrees ik niet, maar voor u, zwakke
spruit, die reeds zoovele tranen over het lijden uws vaders gestort
hebt.”
Nu drukte hij weder hare zachte handen, en haar stijf in de oogen
ziende:
“Zoo gij dáár,” sprak hij, op de deur wijzende, “eenen hoop
soldaten met bloote degens zaagt komen, zoo gij hen met uwen
grijzen vader zaagt weggaan; zeg mij, zoudt gij dan op mijne bede
stil en gerust de uitkomst, gelukkig of ongelukkig, afwachten zonder
mij door uwe tranen het lot nog bitterder te maken? — Geertruid, gij
antwoordt mij niet?”
“Ja, ja, vader,” schreide deze, “ik zal u niet verlaten, en u door
mijne liefde troosten!....”
“Maar zoo gij mij niet volgen moogt, en dat een onbepaald vaarwel
tusschen ons beiden moet uitgesproken worden?”
Heete tranen en pijnlijke snikken waren alleen des meisjes
antwoord.
“Geertruid,” sprak de grijsaard, haar kussende, “wees moedig en
houd u sterk.”
“Neen, neen,” schreide zij, “het lot zal ons zoo hard niet drukken.”
“God geve, dat gij de waarheid zegget,” antwoordde de Geus
twijfelachtig.
Hij klopte met zijne vuist sterk op de tafel. De oude Theresia kwam
op het gerucht binnen.
“Theresia,” sprak Godmaert haar toe, “luister naar mijne bevelen.
Ik ken de liefde, die gij mijne dochter toedraagt. Gij hebt haar lang tot
moeder verstrekt. Misschien zal heden of morgen alles in vuur en
vlam staan, en de straten van Antwerpen zullen misschien met bloed
geverfd worden. Dan zal ik mijne vrienden niet verlaten en mijn
leven, hoe dierbaar het ook zij, voor vaderland en eer in de
waagschaal stellen. Mijne Geertruid beveel ik in uwe handen. Van nu
af zult gij haar niet verlaten; want de wolken drijven ons steeds
onstuimig boven het hoofd.”
Op eens vloog een snijdende schreeuw uit Geertruids borst.
“Och, God! daar zijn ze!” riep zij angstig.
Eene menigte verwarde stemmen deden zich in den doorgang
hooren.
“Kom hier, mijn kind,” sprak Godmaert, “kom hier, dat ik u
omhelze. Ween zoo bitter niet. God zal mij voor ongeluk bewaren!”
Het meisje huilde jammerlijk. Op Godmaerts bevel werd zij door
Theresia met geweld uit de kamer geleid.
“Geertruid,” riep de vader, “misschien bedriegt gij u!”
Doch Geertruid zag de soldaten in het voorbijgaan, en lang
weergalmde hare kleine kamer van hare klachten, totdat zij, zwak en
afgemat, in diepen weemoed sprakeloos lag verzonken.
De hoofdman naderde den Geus en las hem een bevel van den
markgraaf voor, volgens hetwelk hij als staatsgevangene in het
Steen moest gebracht worden. De grijsaard wierp zich den mantel
over de schouders en volgde den hoofdman met onderwerping,
zonder zich eenigszins over zijn lot te beklagen. Bij de deur stonden
twintig wapenbroeders, om hem te geleiden, en eene menigte volks,
die nieuwsgierig wachtte om te zien, wie de gevangene zijn mocht.
Zoodra zij Godmaert ontwaarden en zijn droevig gelaat onder zijne
grijze haren zagen uitschijnen, steeg een schreeuw van verlossing
en wraak uit alle monden, doch de wapenbroeders wederhielden
deze wolk van ongewapende menschen, en brachten den Geus
zonder bloedstorten tot aan het Steen. Hier zag deze den wreeden
Valdès bij de poort staan. Gelukkig dat Godmaert geene wapens bij
zich had, of de Spanjaard hadde zijnen spottenden grimlach met den
dood geboet.
De gevangene werd in eenen diepen en duisteren kelder
gebracht, en na hem de middel met eenen ijzeren gordel omsingeld
en deze in den muur vastgemaakt was, werd hem een stuk brood en
water voorgezet, en de zware deur met gekraak op hem
toegegrendeld.
Dáár zat nu die droeve vader in eenen duisteren kerker, geboeid,
op een weinig vochtig stroo te zuchten. Voor zich zelven was hij niet
bekommerd, mits hij zijn eigen lot nog niet eens berekend had, doch
de tranen zijner lieve Geertruid en het afwezen van dit dierbaar
meisje, dat als eenig kind hem zoo nauw aan ’t harte lag, waren al te
zware slagen om niet onder hun geweld te bukken. Ook zakte hij
wanhopig op zijn stroo neder.
Een vloek van wraak ging op uit zijnen mond, en de zwaar
overwelfde muren herhaalden de woorden: Valdès en verrader, met
een naar en dof geluid.
Terwijl de grijze vader dus angstig aan zijn kind dacht, was
Geertruid, door wanhoop en bittere pijn uitgeput, in een stoel
nedergezakt. Ongeloovig was zij aan dit voorval. Zulk ongeluk
scheen haar al te groot, en menigmaal vroeg zij twijfelend, of het wel
waar was, dat haar oude vader van soldaten was weggevoerd.
Wanneer de dienstbode haar dan een bevestigend antwoord gaf,
stroomden hare tranen harder dan te voren. Jammerlijke klachten en
wanhopige gebaren vermoeiden haar telkens zoodanig dat zij meer
dan eens, afgemat en uitgeweend, zich op den stoel liet nedervallen.
“Lieve Theresia,” schreide zij, “loop naar pater Franciscus hij
alleen kan nog onze engelbewaarder zijn.”
“Maar gij vergeet, jonkvrouw, dat pater Franciscus met den abt
van St. Bernards vertrokken is?”
“O wee, het is waar! Raad mij dan, wat ik doen moet om mijnen
vader te zien. O, raad mij! weet gij geen middel, zeg?”
“Anders geen, jonkvrouw,” antwoordde de dienstbode, “dan den
Steenwarer door woorden of werken pogen te bewegen, dat is te
zeggen door gebeden of geld.”
“Kom aan,” riep Geertruid, “geld heb ik, en woorden zullen mij niet
ontbreken. Door liefde en bittere smart ingegeven, zal ik den
Steenwarer wel tot medelijden brengen.”
“Gij weet niet, jonkvrouw, hoe ongevoelig een gevangenbewaarder
is. En zoo het geld op hem niet werkt, is er weinig hoop over.”
“Kom aan! kom aan!” smeekte het bedrukte meisje vuriger, “al
hadde hij een steenen hart, hij zou immers voor mijne roodbetraande
oogen en mijne krachtige bede moeten wijken.”
“Ik wil gaarne met u uitgaan, om te zien, of wij uwen ongelukkigen
vader troosten mogen; maar houd u ingetogen, en wees voorzichtig
in uwe smart. Laat mij ook eerst uwe kleederen wat opschikken.”
Nu kreeg Geertruid met haast hare zwarte zijden huik op het
hoofd; en daar zij onrustig heen en weer door de kamer stapte, alsof
zij daardoor haren weg vervorderde, vatte Theresia eene harer
handen en zij begaven zich te zamen op weg.
Na vele hoopen menschen, waarvan de een met medelijden, de
ander met koude nieuwsgierigheid ’s meisjes droefheid aanzag,
doorgedrongen te hebben, kwamen zij eindelijk bij de
zwaarbemuurde gevangenis.
“Is mijn vader hier op het Steen?” vroeg Geertruid angstig.
“Ik geloof het vast,” antwoordde de oude Theresia. “Kom,
Geertruid, schep moed. Ik zal kloppen.”
De deur draaide welhaast schreeuwend op hare hengels. Zij
werden in de kleine kamer van den Steenwarer binnengelaten.
“Wat verlangt gij, edele jonkvrouw?” vroeg hij, zich voor Geertruid
buigende.
“Is mijn vader hier?”
“Zoo Godmaert uw vader is, jonkvrouw.”
“Ja, ja, Godmaert. Gij zult gevoelig zijn voor mijne droefheid, en
zeker mij mijnen grijzen vader voor een oogenblik laten troosten. O,
weiger mij niet! Neen, weiger mij niet, ik bid u. Zoo gij ook kinderen
hebt, kunt gij licht bedenken, welken hartdruk ik gevoel. Laat mij de
stem mijns vaders hooren; ik zal u mildelijk beloonen.”
“Jonkvrouw,” antwoordde hij treurig, “het is nog geen half uur
geleden, dat Signor Valdès mij een schriftelijk bevel van den
markgraaf heeft doen geven, om den gevangen Godmaert alle
onderhandelingen met zijne vrienden te beletten. Het drukt mij
evenzeer, dat ik uwe vraag niet kan toestaan.”
Nu smolt de weemoedige Geertruid opnieuw in bittere tranen, en,
des Steenwarers ruwe hand in de hare smeekend drukkende:
“Ik bid u,” schreide zij, “ik bid u, heb medelijden met een kind, dat
aan zijnen vader wreedelijk ontrukt is. O, wees niet ongevoelig! Laat
u mijn bitter schreien door het hart gaan. Gij zijt immers ook een
mensch, en niet van gevoel beroofd: gij kunt immers mijne tranen
niet zonder medelijden aanzien? Och, laat mij toch bij mijnen vader,
of ik verlaat u niet, en zal zoolang weenen, totdat gij zelf, om van
mijne lastige bede ontslagen te zijn, mij in de gevangenis mijns
vaders brengen zult.”
“Och ja, mijnheer,” sprak Theresia, “laat ze toch bij hare vader, of
zij sterft nog van angst.”
Nu klonken de sleutels, die aan des Steenwarers gordel hingen,
de twee smeekende vrouwen, denkende dat hij hunne vraag ging
toestaan, sloegen de handen van blijdschap en opgetogenheid te
zamen, en woorden van dankbaarheid ontvielen reeds hunnen
mond, wanneer de gevangenbewaarder, die achteruit was gegaan,
om eenen traan van zijne wangen te vagen, haar opnieuw naderde.
“Vrouwen,” sprak hij, “uw lijden heeft mij eenen traan uit de oogen
geperst. Dit is een teeken, dat ik ten uiterste in uwe droefheid deel,
doch, daar plicht mij dwingt, kan ik u niet troosten. Denkt niet, dat gij
mij door weenen kunt verbidden. Neen, ik heb lang genoeg
droefheid en bitter lijden gezien, om voor uwe tranen niet meer te
zwichten. Derhalve zeg ik u, dat geen middel, welk het ook zij, mij
mijnen plicht kan doen vergeten. Ik ben een gevangenbewaarder.
Vraagt wat een gevangenbewaarder is, en iedereen zal u
antwoorden: een tijger, en dit is ook zóó. Dit moet zoo zijn.”
Hij liet bij deze woorden de neerslachtige vrouwen staan huilen,
en ging weg.
“Wreedaard!” zuchtte Geertruid, “hoe koud is hij voor onze
droefheid. Theresia, gij hadt gelijk, een gevangenbewaarder is geen
mensch. Kom aan, ik zal bij onze vrienden om hulp zoeken.”
Zij vertrokken met meer droefheid dan zij gekomen waren.
Geertruids eerste gedachte viel op den goeden Schuermans, dien
edelmoedigen, doch armen Geus. Zij wendden zich met haastige
stappen naar het Klapdorp. Dáár werd de deur van een oud
vervallen huis voor haar geopend.
“Och, Schuermans!” riep Geertruid, “weet gij wat mijnen vader
gebeurd is?”
“Ja, jonkvrouw,” antwoordde de Geus, haar binnenlatende, “ik
weet alles. Zwijg, ween niet; want ik kan uwe tranen niet zonder
lijden aanzien. De verrader Valdès heeft dit alles bewerkt. Ik heb
mijnen dolk reeds gewet, daar denkt hij niet aan!”
“Schuermans,” sprak het meisje, “om Gods wil, zeg mij, weet gij
geen middel om mij bij mijnen vader te brengen?”
“Geen,” was het antwoord, “ik heb zelf bij de gevangenis een uur
lang gesmeekt, doch zij zijn onverbiddelijk.”
“Zoek nog eens in uw hoofd of er niet de minste hoop overblijft.
Gij, mannen, weet beter dan wij, wat er te doen staat.”
Schuermans zag de bedrukte Geertruid met medelijden aan
“Arme dochter!” zuchtte hij, en na een oogenblik de hand op het
voorhoofd gehouden te hebben, hief hij wanhopig de schouders op.
“Neen, Geertruid,” hernam hij, “ik weet geen enkel middel. Ik raad u,
ongelukkig meisje, te huis in uwe kamer den uitslag dezer zaak,
zonder meer te weenen, af te wachten. Ik zal zelf bij alle vrienden
gaan, en zoo ik eenige verzachting in uw lijden kan brengen, zal ik
mij met haast naar uwe woning begeven. — Waar is Lodewijk Van
Halmale?” vroeg hij.
“Lodewijk is weg,” antwoordde zij. “Oh! ware Lodewijk hier, ik zou
weldra mijnen vader zien.”
“Waar is hij dan naartoe?”
“Naar Zoersel, om Wolfangh op te zoeken.”
“Oh ja! hij zal toch morgen, bij dageraad, hier zijn. Kom, Geertruid,
stil uw gemoed. Die bittere tranen zullen uw lot niet verzachten.
Denk, dat warme vrienden uws vaders leven angstig bewaken.
Vaarwel, lieve jonkvrouw. Ik zal moeite doen om uw leed in vreugde
te doen veranderen.”
Nu vertrokken de twee vrouwen zonder vertroosting. Zij kwamen
ten uiterste bedrukt in hunne woning terug.
“Wat gaan wij nu doen?” riep Geertruid, zich wanhopig op eenen
stoel werpende.
“Geduld hebben en op God betrouwen,” antwoordde de goede
vrouw. “Gij ziet immers wel, lieve Geertruid, zooals Schuermans
zegt, dat ons de tranen weinig helpen. Laat ons dan niet meer
weenen en Lodewijks komst op goeder hoop afwachten.”
“Weenen!” zuchtte Geertruid, “ik kan toch niet meer weenen, mijne
oogen branden, en bitter hartzeer alleen blijft mij over. Hoe
rampzalig ben ik toch, lieve Theresia. Ik heb immers dit drukkend lot
niet verdiend? Ik, die zoo nauwkeurig mijne plichten jegens God en
de menschen gekweten heb?”
“Geertruid, Geertruid! Wilt gij den Almachtige, den eenigen troost,
die u op aarde overblijft, op u verbitteren, en door gemor uw lijden
verdienen?”
Zij wees met ernstigen toon op de knielbank.
“Jonkvrouw,” sprak zij, “gij hebt gezondigd!....”
Het meisje boog zich gehoorzaam voor het kruis neder en bleef
lang, zeer lang in het gebed. De oude vrouw, die wel wist, dat zij
meer troost in bidden dan in klagen vinden kon, liet haar zonder
stoornis zitten en volgde stilzwijgend hare kniebuiging na.
De zon nu reeds lang onder de kim gedoken zijnde, waren de
Antwerpsche straten in duisternis gedompeld, wanneer de jonge
Geertruid, van de knielbank opstaande en in tranen uitbarstende,
zich aan den hals van Theresia wierp.
“Ik heb niet gebeden!” schreide zij, “ik heb niet eens aan God
gedacht. Ik ben eene schuldige zondares!....”
“Aan wien hebt gij dan gedacht?”
“Aan mijnen vader, aan Lodewijk,” riep Geertruid weenende, “en
God is op mij vergramd; want voor het kruis heb ik geenen troost
gevonden.”
En de oogen stonden haar gansch verdwaald in het hoofd.
“Wel, wel! ongelukkige Geertruid, wat zult gij geworden, arm kind!”
zuchtte Theresia.
Zij drukte het half zinnelooze meisje met medelijden tegen haren
boezem.
“Theresia,” riep deze, “wist ik maar wat mijn vader doet! Hij is
dood. Dit heb ik dáár op de knielbank gedroomd en geloofd; en
daarom heb ik niet gebeden.”
En zij sloeg zich van wanhoop voor de borst, en huilende liep zij
de kamer rond.
“Geertruid! wat doet gij? Gij doolt!”
Doch hare woorden stilden het meisje niet.
“Jonkvrouw!” riep zij harder, “ik herinner mij iets, dat u bij uwen
vader kan doen naderen.”
Nu kwam Geertruid ijlings tot haar geloopen.
“Spreek! lieve Theresia, spreek, wat is het?”
“Weet gij het Jan-van-Lier-straatje, hier juist achter den hoek?”
“Zeker,” antwoordde Geertruid.
“Wel, daar woont een oud grijs wijf; die kan, zoo gij den moed hebt
mij bij haar te volgen, u alles zeggen wat gij zoekt te weten. En zeker
moogt gij zijn, dat de waarheid uit haren mond spreekt.”
“Die oude gebukte vrouw, die door de geburen de tooverheks
wordt geheeten?”
“Ja, dezelfde.”
“Denkt gij, dat deze mij zeggen kan wat mijn vader doet en lijdt?”
“Ja, kind, ik zeg het met schaamte, menigmaal ben ik bij haar ten
rade geweest, en nooit heeft zij een valsch woord voor mij
uitgesproken. Gij zult zien, dat, zonder wij haar van ons ongeluk
onderrichten, zij alles van zelf zal raden.”
Zij verlieten onmiddellijk hunne woning, keerden den hoek om en
bevonden zich in het enge Jan-van-Lier-straatje.
“Wie klopt zoo laat in den nacht aan mijne deur?” werd er
gevraagd.
“Moeder, doe maar open!” antwoordde Theresia, “gij kent immers
uwe buurvrouw nog wel?”
“Wacht een weinig, dat ik mijne lamp ontsteke.”
De deur werd met omzichtigheid en langzaam voor hen geopend.
Na de herkenning werden zij in een klein vertrek gelaten, waarin de
lamp bij hunne komst menigvuldige stralen zond.
Een akelige schreeuw ging uit den mond der verschrikte Geertruid
op, en zij dorst het vertrek niet binnentreden.
“Kom maar binnen, jonkvrouw,” sprak de tooveresse, “ik verzeker
u, dat gij geene reden hebt om te vreezen.”
Nu trad Geertruid bevend in de kamer en drong zich vast tegen
Theresia’s kleederen.
Het was er in wanorde en vuil; twee stoelen stonden er bij eene
zware tafel, waarop een groot boek, een moordpriem, speelkaarten
en eenige geraamten van kleine dieren lagen. Twee pikzwarte katten
zaten ronkend op de stoelen. Hunne bewegingen waren zoo ernstig
en zonderling, dat het scheen, dat deze dieren met verstand begaafd
waren; zij zagen Geertruid met stijve nieuwsgierigheid aan. Een
doodshoofd, waarvan de holle oogen en de blinkende tanden
Geertruid verschrikt hadden, stond vervaarlijk op de schouwplaat.
De tooveresse was een leelijk wijf, dat honderd jaar oud scheen;
diepe rimpels lagen haar over het aangezicht, waarop hare grijze
haarlokken verward rolden. Hare gele oogen waren met ijver op de
angstige Geertruid gevestigd.
“Wat is toch de oorzaak, welke u eene arme vrouw, als ik ben, zoo
laat in den nacht doet bezoeken, edele jonkvrouw?” vroeg zij. “Wilt
gij, dat ik uw lot in de kaarten leze? Nu dan.”
En zij mengde de kaarten terdege ondereen.
Na het krakend gebeente op den vloer gelegd te hebben, spreidde
zij het spel kaarten op de tafel uit. Zij lag eenige oogenblikken in
overdenking, om, zoo goed zij kon, haar orakel aaneen te krijgen;
dan, wanneer zij dacht het gevonden te hebben, sprak zij:
“Ziet gij daar, jonkvrouw?.... Kom toch dichter bij de tafel. Wees
niet bevreesd. Ziet gij dáár, zeg ik, dien schoppenheer?”
“Ja wel,” antwoordde Geertruid.
“Nu, dit is uw vader. Het schijnt, dat hij op dit oogenblik zeer
ongelukkig is. Op de kaart zie ik zijne tranen en de knarsing zijner
tanden.”
Geertruid beefde van schrik en droefheid.
“Wacht dan, jonkvrouwe,” sprak de tooverheks, “wacht! Ziet gij
daar die twee klaveren? Dat is twee dagen lijdens. Die tien, die zich
dáár bevindt, toont aan, dat de pijn groot, onuitsprekelijk groot zal
zijn. Geduld, jonkvrouw, geduld! Het beste komt aan. Stel u gerust.
Daar, ziet gij dien ruitenheer, die daarnevens ligt? Die alleen zal
uwen vader door zijne hulp verlossen.”
“Wie is dat?” vroeg Geertruid.
“Zijnen naam weet ik niet,” was het antwoord, “maar dit weet ik,
dat het een mensch is, die veel kwaad gedaan heeft en als een dier
de bosschen bewoont.”
“Wolfangh!....” zuchtte Geertruid.
“Dáár,” ging de oude vrouw voort, “die hartenboer is een jongeling,
die u teederlijk bemint en aan u sedert dezen morgen onophoudelijk
gedacht heeft.”
“Weet hij wat mijnen vader gebeurd is?” vroeg het meisje.
“Neen, dit weet hij niet, anders zou hij in uw lijden gedeeld
hebben. Hier, nevens hem, hebt gij hartenvrouw. Zie, jonkvrouw, dit
zijt gij zelve, alles toont mij aan, dat gij eens gelukkig met hem zult
zijn. Verder zeggen de ruiten, die dáár liggen, dat er tegenwoordig
veel over uwen vader geschreven wordt, en deze klaverenheer en
die boeren komen mij als rechters voor. Vast geloof ik, dat uw vader
op dit oogenblik ondervraagd wordt. Nog iets weet ik, doch, daar het
u al te pijnlijk zou zijn dit te weten, zal ik verder niets meer zeggen.”
“Wel, nu weten wij nog maar weinig, moeder!” sprak Theresia.
“Hoe?” riep het oude wijf, “weet gij niet, dat uw leed in ’t kort zal
eindigen; en is het niet beter, dat ik de schrikkelijke zaak, die ik nog
weet, verzwijg?”
“Neen,” antwoordde Geertruid, in hare droefheid verbolgen, “zeg
mij alles wat gij weet, en ik zal u rijkelijk beloonen.”
“Wel, gij wilt het zoo, jonkvrouw? Gij hoort het, Theresia, zij wil
alles weten.”
“Nachtboden!” sprak zij, zich tot de katten keerende, “dat mijn wil
geschiede!”
En jammerlijk huilende vlogen de twee zwarte dieren in de
schouw, en verdwenen.
“Och God!” riep het verschrikte meisje, zich tegen Theresia’s borst
klemmende, “het zijn helsche geesten die hier wonen!”
“Gij hebt het gezegd,” antwoordde de tooveresse, “doch wil u
daarom niet verschrikken: er zal u niet het minste leed geschieden.
Ik bid u, mij niet in deze mijne groote werking te storen.”
Zij vatte eenen ijzeren kelk en plaatste hem op eenen vergulden
driepikkel. Een stukje purperen zijde wreef zij driemaal tegen het
doodshoofd, en, na het met zeker water bevochtigd te hebben,
smeet zij het in den beker. Eene blauwe vlam ging kronkelend in de
hoogte. Zij nam dan haar tooverboek, en, na menigmaal hare dorre
handen door de vlam gedreven te hebben, las zij grommelend op
verscheidene bladen des boeks woorden, die eenen
schrikverwekkenden klank bij zich hadden. Driemaal liep zij rondom
de tafel, en riep de helsche geesten tot zich.
De katten kwamen weder huilend de schouw uit.
Het laat zich lichtelijk bedenken, hoe verschrikt het arme meisje
zijn moest; maar, dewijl ander lijden hare krachten verzwakt had,
was zij nu voor hetgeen zij zag, schier ongevoelig geworden.
Theresia rilde in al hare ledematen, doch hare nieuwsgierigheid was
grooter dan hare vrees, en daar zij menigmaal zulke dingen gezien
had, vond zij sterkte genoeg om Geertruid te ondersteunen.
“Wel,” sprak de tooverheks, ’s meisjes hand vattende, “zeg mij nu
of gij, zoo ik u de waarheid zien laat, zeg mij, zoo uw lijden er door
vergroot wordt.... zult gij dan op mij verbitterd zijn?”
“Neen, neen,” antwoordde Geertruid bevende, “ik heb u dit immers
zelve gevraagd.”
“Wilt gij dan eerst uwen minnaar zien?”
“Ja.”
“Kom dan hier, bij de schouw. Oh, gij zijt van de katten bang? —
Vertrekt!” riep zij, en de zwarte dieren liepen haastig de schouw in.
Zij vatte het doodshoofd en legde het op de tafel.
“Kom hier vóór de schouw, jonkvrouw; zie in dezen spiegel.”
En zij trok een gordijntje van voor ’t glas weg.
“Ja, zie, dáár slaapt Lodewijk,” riep Geertruid, “Theresia, kom, zie
hoe rustig hij slaapt! — Zie, een man zit bij hem zorgend te waken.
Theresia, kom dan, ziet gij zijne blonde haarlokken over het
hoofdkussen niet rijzen, en den glimlach, die over zijne lippen
zweeft? — Hij droomt!....”
“Ja,” sprak het oude wijf, “hij droomt van u, jonkvrouw.”
Geertruid bleef lang op den spiegel staren. Zij vond eenigen troost
in den zoeten slaap haars beminden.
“Wel, gelijkt die jonker aan uwen verloofde?” vroeg de tooverheks.
“Ja, ja, het is hij zelf,” zeide Geertruid. “Wanneer zal ik hem
wederzien?”
“Morgen, bij zonneopgang,” was het antwoord.
Geertruid verheugde zich bij de hoop, dat zij Lodewijk haast tot
troost en hulp zou hebben.
“Wilt gij nu uwen vader zien?”
“Ja.”
“Ga dan van vóór den spiegel weg.”
Zij deed het gordijntje nedervallen.
“Jonkvrouw,” ging zij voort, “heb wat geduld, totdat het verschijnsel
zich gevormd hebbe. Eene schrikkelijke zaak zult gij zien; en wellicht
zullen uwe krachten u van angst en druk begeven.”
“Gij bedriegt u, moeder,” sprak Geertruid, “zoo ik mijnen vader
levend zie, zal ik moed genoeg hebben.”
“Wel, jonkvrouw, kom nu voor den spiegel,” sprak de tooveresse,
het gordijntje opheffende.
Geertruid had niet zoodra de oogen er op gewend, of een pijnlijke
schreeuw ontvloog haar, en zij viel zwaar en roerloos op den grond
neder.
Theresia stortte bittere tranen over hare rampzalige meesteresse,
en kermde over de menigvuldige slagen, welke haar dien dag
getroffen hadden.
“Dat wist ik,” zei het oude wijf. “Heb ik het niet gezegd? Doch ik zal
deze onmacht wel overwinnen.”
“Wat heeft zij dan gezien?” vroeg Theresia.
“Zie gij zelve,” sprak de tooveresse, haar voor den spiegel
brengende.
Theresia week schreeuwend achteruit.
Wat zagen zij dan?.... Den grijzen Godmaert, van beulen omringd
en op eene vervaarlijke wijze gepijnigd. De uitdrukking zijner
stuiptrekkende wangen en het bloed, dat hem over het lichaam liep,
hadden de harten dezer vrouwen verbrijzeld.
“Wat ga ik nu met mijne roerlooze meesteresse doen?” vroeg
Theresia snikkend.
“Luister,” antwoordde het oude wijf, “hier heb ik een fleschken, dat
alles terecht zal maken. Na ik haar dit zal ingegeven hebben, zal de
jonkvrouw opstaan en u naar uwe woning volgen. Eene diepe
vergetelheid van het verledene zal ik over haar halen. Leg haar dan
te bed, en de stem haars minnaars zal alleen de kracht hebben om
haar uit den slaap te roepen. Ik hoop, dat, wanneer alles zal
uitgevallen zijn, gelijk ik het u voorzegd heb, gij mij dan niet vergeten
zult.”
Zij goot langzaam den inhoud van het fleschken in Geertruids
mond. Deze richtte zich op en bleef stilzwijgend staan.
“Ga voor, Theresia!” sprak het oude wijf. “Wees niet voor de
jonkvrouw bevreesd, zij zal u op de hielen volgen. Vaarwel! spreek
haar niet aan, zij hoort u niet.”
En de deur werd achter de beide vrouwen gesloten.
Theresia stapte bevend voort en, angstig omziende, bemerkte zij,
dat Geertruid haar gestadig volgde. Wanneer zij nu in hunne woning
terug en bij Geertruids bed waren, liet deze zich geduldig
ontkleeden. Zij was zoodra niet gelegen, of een diepe slaap sloot
hare roodbeschreide oogen.
Theresia waakte bij een klein licht, doch weldra kwam de
vermoeidheid over hare zorg zegepralen, en zij viel op den stoel in
slaap.
VI
O, mensche, wie ghy zyt en toont u niet ’t onvreden,
Als God doorwont u hart met druck en swaricheden.

jacob cats.

Laat ons nu een weinig in ons verhaal terugkeeren, om te zien, of


de waarzegster terecht was, wanneer zij Geertruid haren vader in
zulk eenen akeligen toestand voorstelde.
De Spanjaard Valdès had de oplichting van Godmaert nauwkeurig
bijgewoond en met vrees bemerkt, dat het gemeene volk den Geus
zeer was toegedaan. Angst had hem getroffen op het oogenblik, dat
de morrende verlossingskreet was opgegaan. Maar wanneer hij de
deur der gevangenis op zijnen vijand had zien toesluiten, vertrok hij
om de gevolgen zijner beschuldigingen te verhaasten.
Godmaert zat in den hoek van eenen kerker, die geene
gemeenschap had met licht of lucht. Bittere tranen rolden over zijne
wangen, daar hij aan de smart zijner dochter dacht, en mits grootere
hartpijn hem drukte, voelde hij niet, dat zijne bewegingen van
wanhoop den zwaren ijzeren gordel in zijne lenden prentten. De tijd,
dien hij reeds in dit donker hol had doorgebracht, scheen hem lang,
zeer lang te zijn, alhoewel de avondzon de buitenmuren van het
Steen nog kleurde met haren rooden glans.
Om tien uren des avonds werd de deur des kerkers geopend.
“Godmaert!” riep de Steenwarer, met zijne lantaarn den kerker
binnentredende, “sta op, ik moet u voor den rechterstoel leiden.”
En hij sloot den ijzeren gordel. Twee gewapende mannen namen
den grijsaard bij den arm, en brachten hem door duistere gangen tot
in eene zaal, welke als de beuk eener kerk was overwelfd. Zeer laag
was het verdiep, want de zuilen, waarop de welfbogen rustten,
waren weinig verheven, dus kon de lamp, die rookend op de tafel
brandde, gemakkelijk het welfsel bereiken en de plaats ten beste
verlichten. Een groot kruisbeeld, kunstig met zwart en rood hout
ingelegd, en een open evangelieboek met zilveren sloten lagen op
het tapijt der tafel. Twee dolken waren kruiswijze, ten teeken van
bloedig recht, op de bladen van het evangelie geplaatst.
Vier personen, gansch in zwarte stof gekleed, zaten bij eene
tweede tafel, aan hun ernstig en koud gelaat kon men bemerken, dat
zij de rechters waren. Papier en pennen lagen vóór hen op de tafel,
tot het aanteekenen der bekentenissen, die zij van den beschuldigde
verwachtten. Bij de deur der zaal stonden twee gewapende dienaars
met bloot slagzwaard.
Verder in het verschiet, bij het twijfelachtig licht kon men eenige
werktuigen bespeuren, die zonder orde, het een op het ander, tegen
den grond lagen, men bemerkte er raderen, koorden, banken,
kettingen, tusschen andere onherkennelijke dingen. Dit waren de
werktuigen der pijnbank, welke alsdan in alle hooge rechtsplegingen
gebruikt werd om den beschuldigde tot de bekentenis zijner
misdaden te dwingen.
Godmaert liet zijn oog met afschrik op die bloedige
gereedschappen der wet vallen; maar eene onstuimigere beweging
schokte zijne ledematen, wanneer hij zijn gezicht in eenen donkeren
hoek der zaal stuurde, hij had in de verte, als een akelig verschijnsel,
de wezenstrekken van zijnen vijand Valdès herkend!
“Laat den gevangene tot ons komen!” riep een der rechters, en
Godmaert werd door de gewapende mannen tot op eenen kleinen
afstand der schrijftafel geleid.
Na eenige oogenblikken tot zijne mederechters gesproken te
hebben, keerde de voorzitter zich tot Godmaert en zeide:
“Nader mij, hier bij de tafel. Zweer met de hand op dit beeld onzes
Zaligmakers en op het boek des levens, dat gij de waarheid voor ons
zult zeggen, en niets dan de waarheid.”
“Dit zweer ik bij den God, die ons hoort!” sprak Godmaert, de hand
op het kruis leggende.
Welcome to our website – the ideal destination for book lovers and
knowledge seekers. With a mission to inspire endlessly, we offer a
vast collection of books, ranging from classic literary works to
specialized publications, self-development books, and children's
literature. Each book is a new journey of discovery, expanding
knowledge and enriching the soul of the reade

Our website is not just a platform for buying books, but a bridge
connecting readers to the timeless values of culture and wisdom. With
an elegant, user-friendly interface and an intelligent search system,
we are committed to providing a quick and convenient shopping
experience. Additionally, our special promotions and home delivery
services ensure that you save time and fully enjoy the joy of reading.

Let us accompany you on the journey of exploring knowledge and


personal growth!

ebooknice.com

You might also like