Tutorial

This notebook gets you started with using Text-Fabric for coding in the Old-Babylonian Letter corpus (cuneiform).

Familiarity with the underlying data model is recommended.

Installing Text-Fabric

See here

Tip

If you start computing with this tutorial, first copy its parent directory to somewhere else, outside your copy of the repositorty If you pull changes from the repository later, your work will not be overwritten. Where you put your tutorial directory is up to you. It will work from any directory.

Old Babylonian data

Text-Fabric will fetch the data set for you from the newest github release binaries.

The data will be stored in the text-fabric-data in your home directory.

Features

The data of the corpus is organized in features. They are columns of data. Think of the corpus as a gigantic spreadsheet, where row 1 corresponds to the first sign, row 2 to the second sign, and so on, for all 200,000 signs.

The information which reading each sign has, constitutes a column in that spreadsheet. The Old Babylonian corpus contains nearly 60 columns, not only for the signs, but also for thousands of other textual objects, such as clusters, lines, columns, faces, documents.

Instead of putting that information in one big table, the data is organized in separate columns. We call those columns features.

In [2]:
%load_ext autoreload
%autoreload 2
In [3]:
import os
import collections

Incantation

The simplest way to get going is by this incantation:

In [4]:
from tf.app import use

For the very last version, use hot.

For the latest release, use latest.

If you have cloned the repos (TF app and data), use clone.

If you do not want/need to upgrade, leave out the checkout specifiers.

In [6]:
A = use("Nino-cunei/oldbabylonian", hoist=globals())
rate limit is 5000 requests per hour, with 4614 left for this hour
	connecting to online GitHub repo Nino-cunei/oldbabylonian ... connected
TF-app: ~/text-fabric-data/Nino-cunei/oldbabylonian/app
data: ~/text-fabric-data/Nino-cunei/oldbabylonian/tf/1.0.6
This is Text-Fabric 9.2.0
Api reference : https://annotation.github.io/text-fabric/tf/cheatsheet.html

67 features found and 0 ignored
Text-Fabric: Text-Fabric API 9.2.0, Nino-cunei/oldbabylonian/app v3, Search Reference
Data: OLDBABYLONIAN, Character table, Feature docs
Features:
Old Babylonian Letters 1900-1600: Cuneiform tablets
ARK
str
persistent identifier of type ARK from metadata field "UCLA Library ARK"
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:07Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
what comes after a sign or word (- or space)
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:07Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
what comes after a sign or word (- or space); between adjacent signs a ␣ is inserted
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:07Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
what comes after a sign when represented as unicode (space)
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:07Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
atf
str
full atf of a sign (without cluster chars) or word (including cluster chars)
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:07Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
atf of cluster closings at sign
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:07Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
atf of cluster openings at sign
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
author from metadata field "Author(s)"
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
col
int
ATF column number
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
int
whether a sign is collated (*)
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
collection of a document
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
$ comment to line or inline comment to slot ($ and $)
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
int
whether a sign is damaged
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
det
int
whether a sign is a determinative gloss - between braces { }
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
additional remarks in the document identification
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
number of a document within a collection-volume
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
excavation number from metadata field "Excavation no."
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
int
whether a sign is excised - between double angle brackets << >>
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
full name of a face including the enclosing object
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
sequence of flags after a sign
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
fraction of a numeral
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
genre from metadata field "Genre"
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
grapheme of a sign
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
grapheme of a sign using non-ascii characters
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
grapheme of a sign using cuneiform unicode characters
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
language of a document
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
int
1 if a sign is in the alternate language (i.e. Sumerian) - between underscores _ _
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
ln
int
ATF line number of a numbered line, without prime
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
lnc
str
ATF line identification of a comment line ($)
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
ATF line number, may be $ or #, with prime; column number prepended
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
material indication from metadata field "Material"
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
int
whether a sign is missing - between square brackets [ ]
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
museum code from metadata field "Museum no."
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
museum name from metadata field "Collection"
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
name of an object of a document
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
the ! or x in a !() or x() construction
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
the ! or x in a !() or x() construction, represented as =, ␣
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
the ! or x in a !() or x() construction, represented as =, ␣
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
period indication from metadata field "Period"
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
P number of a document
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
int
whether a prime is present on a column number
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
int
whether a prime is present on a line number
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
publication date from metadata field "Publication date"
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
int
whether a sign has the question flag (?)
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
reading of a sign
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
reading of a sign using non-ascii characters
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
reading of a sign using cuneiform unicode characters
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:08Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
int
whether a sign is remarkable (!)
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:09Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
# comment to line
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:09Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
int
repeat of a numeral; the value n (unknown) is represented as -1
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:09Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
full line in source file
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:09Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
int
line number in source file
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:09Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
source file name of a document
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:09Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
genre from metadata field "Sub-genre"
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:09Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
int
whether a sign is supplied - between angle brackets < >
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:09Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
sym
str
essential part of a sign or of a word
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:09Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
essential part of a sign or of a word using non-ascii characters
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:09Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
essential part of a sign or of a word using cuneiform unicode characters
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:09Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
int
whether a line has a translation
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:10Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
person who did the encoding into ATF from metadata field "ATF source"
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:10Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
translation of line in language en = English
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:10Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
str
name of a type of cluster or kind of sign
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:10Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
int
whether a sign is uncertain - between brackets ( )
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:10Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
int
volume of a document within a collection
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:10Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
none
converters:
Cale Johnson, Dirk Roorda
dateWritten:
2020-06-26T09:20:10Z
editor:
Cale Johnson et al.
institute:
CDL
name:
AbB Old Babylonian Cuneiform
writtenBy:
Text-Fabric
Text-Fabric API: names N F E L T S C TF directly usable

You can see which features have been loaded, and if you click on a feature name, you find its documentation. If you hover over a name, you see where the feature is located on your system.

API

The result of the incantation is that we have a bunch of special variables at our disposal that give us access to the text and data of the corpus.

At this point it is helpful to throw a quick glance at the text-fabric API documentation (see the links under API Members above).

The most essential thing for now is that we can use F to access the data in the features we've loaded. But there is more, such as N, which helps us to walk over the text, as we see in a minute.

The API members above show you exactly which new names have been inserted in your namespace. If you click on these names, you go to the API documentation for them.

Text-Fabric contains a flexible search engine, that does not only work for the data, of this corpus, but also for other corpora and data that you add to corpora.

Search is the quickest way to come up-to-speed with your data, without too much programming.

Jump to the dedicated search search tutorial first, to whet your appetite.

The real power of search lies in the fact that it is integrated in a programming environment. You can use programming to:

  • compose dynamic queries
  • process query results

Therefore, the rest of this tutorial is still important when you want to tap that power. If you continue here, you learn all the basics of data-navigation with Text-Fabric.

Counting

In order to get acquainted with the data, we start with the simple task of counting.

Count all nodes

We use the N.walk() generator to walk through the nodes.

We compared the TF data to a gigantic spreadsheet, where the rows correspond to the signs. In Text-Fabric, we call the rows slots, because they are the textual positions that can be filled with signs.

We also mentioned that there are also other textual objects. They are the clusters, lines, faces and documents. They also correspond to rows in the big spreadsheet.

In Text-Fabric we call all these rows nodes, and the N() generator carries us through those nodes in the textual order.

Just one extra thing: the info statements generate timed messages. If you use them instead of print you'll get a sense of the amount of time that the various processing steps typically need.

In [5]:
A.indent(reset=True)
A.info("Counting nodes ...")

i = 0
for n in N.walk():
    i += 1

A.info("{} nodes".format(i))
  0.00s Counting nodes ...
  0.05s 334667 nodes

Here you see it: over 300,000 nodes.

What are those nodes?

Every node has a type, like sign, or line, face. But what exactly are they?

Text-Fabric has two special features, otype and oslots, that must occur in every Text-Fabric data set. otype tells you for each node its type, and you can ask for the number of slots in the text.

Here we go!

In [6]:
F.otype.slotType
Out[6]:
'sign'
In [7]:
F.otype.maxSlot
Out[7]:
203219
In [8]:
F.otype.maxNode
Out[8]:
334667
In [9]:
F.otype.all
Out[9]:
('document', 'face', 'line', 'word', 'cluster', 'sign')
In [10]:
C.levels.data
Out[10]:
(('document', 158.14708171206226, 226669, 227953),
 ('face', 71.70748059280169, 227954, 230787),
 ('line', 7.423525114155251, 230788, 258162),
 ('word', 2.6436180641788116, 258163, 334667),
 ('cluster', 1.782122905027933, 203220, 226668),
 ('sign', 1, 1, 203219))

This is interesting: above you see all the textual objects, with the average size of their objects, the node where they start, and the node where they end.

Count individual object types

This is an intuitive way to count the number of nodes in each type. Note in passing, how we use the indent in conjunction with info to produce neat timed and indented progress messages.

In [11]:
A.indent(reset=True)
A.info("counting objects ...")

for otype in F.otype.all:
    i = 0

    A.indent(level=1, reset=True)

    for n in F.otype.s(otype):
        i += 1

    A.info("{:>7} {}s".format(i, otype))

A.indent(level=0)
A.info("Done")
  0.00s counting objects ...
   |     0.00s    1285 documents
   |     0.00s    2834 faces
   |     0.00s   27375 lines
   |     0.01s   76505 words
   |     0.00s   23449 clusters
   |     0.02s  203219 signs
  0.04s Done

Viewing textual objects

You can use the A API (the extra power) to display cuneiform text.

See the display tutorial.

Feature statistics

F gives access to all features. Every feature has a method freqList() to generate a frequency list of its values, higher frequencies first. Here are the repeats of numerals (the -1 comes from a n(rrr):

In [12]:
F.repeat.freqList()
Out[12]:
((1, 877),
 (2, 398),
 (5, 246),
 (3, 239),
 (4, 152),
 (6, 67),
 (8, 40),
 (7, 26),
 (9, 15),
 (-1, 3))

Signs have types and clusters have types. We can count them separately:

In [13]:
F.type.freqList("cluster")
Out[13]:
(('langalt', 7600),
 ('missing', 7572),
 ('det', 6794),
 ('uncertain', 1183),
 ('supplied', 231),
 ('excised', 69))
In [14]:
F.type.freqList("sign")
Out[14]:
(('reading', 188292),
 ('unknown', 8761),
 ('numeral', 2184),
 ('ellipsis', 1617),
 ('grapheme', 1272),
 ('commentline', 969),
 ('complex', 122),
 ('comment', 2))

Finally, the flags:

In [15]:
F.flags.freqList()
Out[15]:
(('#', 9830),
 ('?', 421),
 ('#?', 131),
 ('!', 91),
 ('*', 9),
 ('?#', 7),
 ('#!', 5),
 ('!*', 2),
 ('#*', 1),
 ('?!*', 1))

Word matters

Top 20 frequent words

We represent words by their essential symbols, collected in the feature sym (which also exists for signs).

In [16]:
for (w, amount) in F.sym.freqList("word")[0:20]:
    print(f"{amount:>5} {w}")
 7089 x
 4071 a-na
 2517 u3
 2361 sza
 1645 um-ma
 1440 i-na
 1365 ...
 1140 qi2-bi2-ma
 1075 la
  796 u2-ul
  776 d⁼utu
  626 d⁼marduk
  585 ku3-babbar
  551 ki-ma
  540 asz-szum
  407 hi-a
  388 lu
  381 1(disz)
  363 ki-a-am
  341 szum-ma

Word distribution

Let's do a bit more fancy word stuff.

Hapaxes

A hapax can be found by picking the words with frequency 1

We print 20 hapaxes.

In [17]:
for w in [w for (w, amount) in F.sym.freqList("word") if amount == 1][0:20]:
    print(f'"{w}"')
"...-BU-szu"
"...-BU-um"
"...-DI"
"...-IG-ti-sza"
"...-SZI"
"...-ZU"
"...-a-tim"
"...-ab-ba-lam"
"...-am-ma"
"...-an"
"...-ar"
"...-ar-ra"
"...-ba-lam"
"...-da-an-ni"
"...-d⁼en-lil2"
"...-d⁼la-ga-ma-al"
"...-ha-ar"
"...-hu"
"...-im"
"...-ir"

Small occurrence base

The occurrence base of a word are the documents in which occurs.

We compute the occurrence base of each word.

In [18]:
occurrenceBase = collections.defaultdict(set)

for w in F.otype.s("word"):
    pNum = T.sectionFromNode(w)[0]
    occurrenceBase[F.sym.v(w)].add(pNum)

An overview of how many words have how big occurrence bases:

In [19]:
occurrenceSize = collections.Counter()

for (w, pNums) in occurrenceBase.items():
    occurrenceSize[len(pNums)] += 1

occurrenceSize = sorted(
    occurrenceSize.items(),
    key=lambda x: (-x[1], x[0]),
)

for (size, amount) in occurrenceSize[0:10]:
    print(f"base size {size:>4} : {amount:>5} words")
print("...")
for (size, amount) in occurrenceSize[-10:]:
    print(f"base size {size:>4} : {amount:>5} words")
base size    1 : 11957 words
base size    2 :  1817 words
base size    3 :   745 words
base size    4 :   367 words
base size    5 :   229 words
base size    6 :   150 words
base size    7 :   128 words
base size    9 :    75 words
base size    8 :    74 words
base size   10 :    64 words
...
base size  459 :     1 words
base size  624 :     1 words
base size  626 :     1 words
base size  649 :     1 words
base size  736 :     1 words
base size  967 :     1 words
base size 1031 :     1 words
base size 1119 :     1 words
base size 1169 :     1 words
base size 1255 :     1 words

Let's give the predicate private to those words whose occurrence base is a single document.

In [20]:
privates = {w for (w, base) in occurrenceBase.items() if len(base) == 1}
len(privates)
Out[20]:
11957

Peculiarity of documents

As a final exercise with words, lets make a list of all documents, and show their

  • total number of words
  • number of private words
  • the percentage of private words: a measure of the peculiarity of the document
In [21]:
docList = []

empty = set()
ordinary = set()

for d in F.otype.s("document"):
    pNum = T.documentName(d)
    words = {F.sym.v(w) for w in L.d(d, otype="word")}
    a = len(words)
    if not a:
        empty.add(pNum)
        continue
    o = len({w for w in words if w in privates})
    if not o:
        ordinary.add(pNum)
        continue
    p = 100 * o / a
    docList.append((pNum, a, o, p))

docList = sorted(docList, key=lambda e: (-e[3], -e[1], e[0]))

print(f"Found {len(empty):>4} empty documents")
print(f"Found {len(ordinary):>4} ordinary documents (i.e. without private words)")
Found    7 empty documents
Found   30 ordinary documents (i.e. without private words)
In [22]:
print(
    "{:<20}{:>5}{:>5}{:>5}\n{}".format(
        "document",
        "#all",
        "#own",
        "%own",
        "-" * 35,
    )
)

for x in docList[0:20]:
    print("{:<20} {:>4} {:>4} {:>4.1f}%".format(*x))
print("...")
for x in docList[-20:]:
    print("{:<20} {:>4} {:>4} {:>4.1f}%".format(*x))
document             #all #own %own
-----------------------------------
P292935                20   11 55.0%
P510702                39   21 53.8%
P386445                40   20 50.0%
P510808                28   14 50.0%
P313381                24   12 50.0%
P510849                16    8 50.0%
P510657                 4    2 50.0%
P292931                48   23 47.9%
P305788                17    8 47.1%
P313326                45   21 46.7%
P292992                28   13 46.4%
P292810                26   12 46.2%
P305774                13    6 46.2%
P510641                13    6 46.2%
P292984                24   11 45.8%
P481778                24   11 45.8%
P373043               104   47 45.2%
P491917                29   13 44.8%
P355749               226  100 44.2%
P365938                25   11 44.0%
...
P510540                21    1  4.8%
P372962                22    1  4.5%
P275094                24    1  4.2%
P372895                24    1  4.2%
P385995                24    1  4.2%
P510732                24    1  4.2%
P386005                27    1  3.7%
P510730                27    1  3.7%
P372914                28    1  3.6%
P372900                30    1  3.3%
P372929                30    1  3.3%
P510528                30    1  3.3%
P413618                31    1  3.2%
P510661                64    2  3.1%
P510535                32    1  3.1%
P365118                34    1  2.9%
P510663                34    1  2.9%
P510817                36    1  2.8%
P275114                40    1  2.5%
P372420                61    1  1.6%

Locality API

We travel upwards and downwards, forwards and backwards through the nodes. The Locality-API (L) provides functions: u() for going up, and d() for going down, n() for going to next nodes and p() for going to previous nodes.

These directions are indirect notions: nodes are just numbers, but by means of the oslots feature they are linked to slots. One node contains an other node, if the one is linked to a set of slots that contains the set of slots that the other is linked to. And one if next or previous to an other, if its slots follow or precede the slots of the other one.

L.u(node) Up is going to nodes that embed node.

L.d(node) Down is the opposite direction, to those that are contained in node.

L.n(node) Next are the next adjacent nodes, i.e. nodes whose first slot comes immediately after the last slot of node.

L.p(node) Previous are the previous adjacent nodes, i.e. nodes whose last slot comes immediately before the first slot of node.

All these functions yield nodes of all possible otypes. By passing an optional parameter, you can restrict the results to nodes of that type.

The result are ordered according to the order of things in the text.

The functions return always a tuple, even if there is just one node in the result.

Going up

We go from the first word to the document it contains. Note the [0] at the end. You expect one document, yet L returns a tuple. To get the only element of that tuple, you need to do that [0].

If you are like me, you keep forgetting it, and that will lead to weird error messages later on.

In [23]:
firstDoc = L.u(1, otype="document")[0]
print(firstDoc)
226669

And let's see all the containing objects of sign 3:

In [24]:
s = 3
for otype in F.otype.all:
    if otype == F.otype.slotType:
        continue
    up = L.u(s, otype=otype)
    upNode = "x" if len(up) == 0 else up[0]
    print("sign {} is contained in {} {}".format(s, otype, upNode))
sign 3 is contained in document 226669
sign 3 is contained in face 227954
sign 3 is contained in line 230788
sign 3 is contained in word 258164
sign 3 is contained in cluster 203222

Going next

Let's go to the next nodes of the first document.

In [25]:
afterFirstDoc = L.n(firstDoc)
for n in afterFirstDoc:
    print(
        "{:>7}: {:<13} first slot={:<6}, last slot={:<6}".format(
            n,
            F.otype.v(n),
            E.oslots.s(n)[0],
            E.oslots.s(n)[-1],
        )
    )
secondDoc = L.n(firstDoc, otype="document")[0]
    348: sign          first slot=348   , last slot=348   
 258314: word          first slot=348   , last slot=349   
 230824: line          first slot=348   , last slot=355   
 227956: face          first slot=348   , last slot=482   
 226670: document      first slot=348   , last slot=500   

Going previous

And let's see what is right before the second document.

In [26]:
for n in L.p(secondDoc):
    print(
        "{:>7}: {:<13} first slot={:<6}, last slot={:<6}".format(
            n,
            F.otype.v(n),
            E.oslots.s(n)[0],
            E.oslots.s(n)[-1],
        )
    )
 226669: document      first slot=1     , last slot=347   
 227955: face          first slot=164   , last slot=347   
 230823: line          first slot=330   , last slot=347   
 203293: cluster       first slot=345   , last slot=347   
 258313: word          first slot=347   , last slot=347   
    347: sign          first slot=347   , last slot=347   

Going down

We go to the faces of the first document, and just count them.

In [27]:
faces = L.d(firstDoc, otype="face")
print(len(faces))
2

The first line

We pick two nodes and explore what is above and below them: the first line and the first word.

In [28]:
for n in [
    F.otype.s("word")[0],
    F.otype.s("line")[0],
]:
    A.indent(level=0)
    A.info("Node {}".format(n), tm=False)
    A.indent(level=1)
    A.info("UP", tm=False)
    A.indent(level=2)
    A.info("\n".join(["{:<15} {}".format(u, F.otype.v(u)) for u in L.u(n)]), tm=False)
    A.indent(level=1)
    A.info("DOWN", tm=False)
    A.indent(level=2)
    A.info("\n".join(["{:<15} {}".format(u, F.otype.v(u)) for u in L.d(n)]), tm=False)
A.indent(level=0)
A.info("Done", tm=False)
Node 258163
   |   UP
   |      |   203220          cluster
   |      |   230788          line
   |      |   227954          face
   |      |   226669          document
   |   DOWN
   |      |   203220          cluster
   |      |   1               sign
   |      |   2               sign
Node 230788
   |   UP
   |      |   227954          face
   |      |   226669          document
   |   DOWN
   |      |   258163          word
   |      |   203220          cluster
   |      |   1               sign
   |      |   2               sign
   |      |   258164          word
   |      |   203221          cluster
   |      |   203222          cluster
   |      |   3               sign
   |      |   4               sign
   |      |   5               sign
   |      |   203223          cluster
   |      |   6               sign
   |      |   7               sign
Done

Text API

So far, we have mainly seen nodes and their numbers, and the names of node types. You would almost forget that we are dealing with text. So let's try to see some text.

In the same way as F gives access to feature data, T gives access to the text. That is also feature data, but you can tell Text-Fabric which features are specifically carrying the text, and in return Text-Fabric offers you a Text API: T.

Formats

Cuneiform text can be represented in a number of ways:

  • original ATF, with bracketings and flags
  • essential symbols: readings and graphemes, repeats and fractions (of numerals), no flags, no clusterings
  • unicode symbols

If you wonder where the information about text formats is stored: not in the program text-fabric, but in the data set. It has a feature otext, which specifies the formats and which features must be used to produce them. otext is the third special feature in a TF data set, next to otype and oslots. It is an optional feature. If it is absent, there will be no T API.

Here is a list of all available formats in this data set.

In [29]:
sorted(T.formats)
Out[29]:
['layout-orig-rich',
 'layout-orig-unicode',
 'text-orig-full',
 'text-orig-plain',
 'text-orig-rich',
 'text-orig-unicode']

Using the formats

The T.text() function is central to get text representations of nodes. Its most basic usage is

T.text(nodes, fmt=fmt)

where nodes is a list or iterable of nodes, usually word nodes, and fmt is the name of a format. If you leave out fmt, the default text-orig-full is chosen.

The result is the text in that format for all nodes specified:

In [30]:
T.text([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], fmt="text-orig-plain")
Out[30]:
'a-na d⁼suen-i-din-namqi2-bi2-maum-'

There is also another usage of this function:

T.text(node, fmt=fmt)

where node is a single node. In this case, the default format is ntype-orig-full where ntype is the type of node.

If the format is defined in the corpus, it will be used. Otherwise, the word nodes contained in node will be looked up and represented with the default format text-orig-full.

In this way we can sensibly represent a lot of different nodes, such as documents, faces, lines, clusters, words and signs.

We compose a set of example nodes and run T.text on them:

In [31]:
exampleNodes = [
    F.otype.s("sign")[0],
    F.otype.s("word")[0],
    F.otype.s("cluster")[0],
    F.otype.s("line")[0],
    F.otype.s("face")[0],
    F.otype.s("document")[0],
]
exampleNodes
Out[31]:
[1, 258163, 203220, 230788, 227954, 226669]
In [32]:
for n in exampleNodes:
    print(f"This is {F.otype.v(n)} {n}:")
    print(T.text(n))
    print("")
This is sign 1:
[a-

This is word 258163:
[a-na] 

This is cluster 203220:
[a-na] 

This is line 230788:
[a-na] _{d}suen_-i-[din-nam]

This is face 227954:
[a-na] _{d}suen_-i-[din-nam]qi2-bi2-[ma]um-ma _{d}en-lil2_-sza-du-u2-ni-ma_{d}utu_ u3 _{d}[marduk]_ a-na da-ri-a-[tim]li-ba-al-li-t,u2-u2-ka{disz}sze-ep-_{d}suen a2-gal2 [dumu] um-mi-a-mesz_ki-a-am u2-lam-mi-da-an-ni um-[ma] szu-u2-[ma]{disz}sa-am-su-ba-ah-li sza-pi2-ir ma-[tim]2(esze3) _a-sza3_ s,i-[bi]-it {disz}[ku]-un-zu-lum _sza3-gud__a-sza3 a-gar3_ na-ag-[ma-lum] _uru_ x x x{ki}sza _{d}utu_-ha-zi-[ir] isz-tu _mu 7(disz) kam_ id-di-nu-szumu3 i-na _uru_ x-szum{ki} sza-ak-nu id-di-a-am-ma2(esze3) _a-sza3 szuku_ i-li-ib-bu s,i-bi-it _nagar-mesz__a-sza3 a-gar3 uru_ ra-bu-um x [...]x x x x x x [...]$ rest broken

This is document 226669:
[a-na] _{d}suen_-i-[din-nam]qi2-bi2-[ma]um-ma _{d}en-lil2_-sza-du-u2-ni-ma_{d}utu_ u3 _{d}[marduk]_ a-na da-ri-a-[tim]li-ba-al-li-t,u2-u2-ka{disz}sze-ep-_{d}suen a2-gal2 [dumu] um-mi-a-mesz_ki-a-am u2-lam-mi-da-an-ni um-[ma] szu-u2-[ma]{disz}sa-am-su-ba-ah-li sza-pi2-ir ma-[tim]2(esze3) _a-sza3_ s,i-[bi]-it {disz}[ku]-un-zu-lum _sza3-gud__a-sza3 a-gar3_ na-ag-[ma-lum] _uru_ x x x{ki}sza _{d}utu_-ha-zi-[ir] isz-tu _mu 7(disz) kam_ id-di-nu-szumu3 i-na _uru_ x-szum{ki} sza-ak-nu id-di-a-am-ma2(esze3) _a-sza3 szuku_ i-li-ib-bu s,i-bi-it _nagar-mesz__a-sza3 a-gar3 uru_ ra-bu-um x [...]x x x x x x [...]$ rest broken$ beginning broken[x x] x x [...][x x] x [...][x x] x s,i-bi-it _gir3-se3#-ga#_[x x] x x x-ir ub-lamin-na-me-er-max _[a-sza3_ s,i]-bi-it ku-un-zu-lum_a-[sza3 a-gar3_ na-ag]-ma-lum _uru gan2_ x x{ki}a-[na] sa-[am-su]-ba-ah-[la] x x li ig bumi-im-ma _a-sza3_ s,i-bi-[it] _nagar-mesz_u2-ul na-di-isz-szuma-na ki-ma i-[na] _dub e2-gal_-limsza _{d}utu_-ha-zi-ir ub-lam in-na-am-ruasz-t,u3-ra-am _dub_ [usz]-ta-bi-la-kuma-wa-tim sza ma-ah-hi-rum u2-lam-ma-du-u2-maasz-szum _e2-gal_-lim la lum-mu-[di ...]_dub_-pi2 u2-sza-ab-ba-x [...]x ti u2-ul a-ga-am-ma-[x ...]u2-ul a-sza-ap-pa-[x ...][at-ta] ki-ma ti-du-u2 wa-ar-ka-az-zu pu-ru-us [x x ...]

Using the formats

Now let's use those formats to print out the first line in this corpus.

Note that only the formats starting with text- are usable for this.

For the layout- formats, see display.

In [33]:
for fmt in sorted(T.formats):
    if fmt.startswith("text-"):
        print("{}:\n\t{}".format(fmt, T.text(range(1, 12), fmt=fmt)))
text-orig-full:
	[a-na] _{d}suen_-i-[din-nam]qi2-bi2-[ma]um-
text-orig-plain:
	a-na d⁼suen-i-din-namqi2-bi2-maum-
text-orig-rich:
	a-na d⁼suen-i-din-namqi₂-bi₂-maum-
text-orig-unicode:
	𒀀𒈾 𒀭𒂗𒍪𒄿𒁷𒉆𒆠𒉈𒈠𒌝

If we do not specify a format, the default format is used (text-orig-full).

In [34]:
T.text(range(1, 12))
Out[34]:
'[a-na] _{d}suen_-i-[din-nam]qi2-bi2-[ma]um-'
In [35]:
firstLine = F.otype.s("line")[0]
T.text(firstLine)
Out[35]:
'[a-na] _{d}suen_-i-[din-nam]'
In [36]:
T.text(firstLine, fmt="text-orig-unicode")
Out[36]:
'𒀀𒈾 𒀭𒂗𒍪𒄿𒁷𒉆'

The important things to remember are:

  • you can supply a list of slot nodes and get them represented in all formats
  • you can get non-slot nodes n in default format by T.text(n)
  • you can get non-slot nodes n in other formats by T.text(n, fmt=fmt, descend=True)

Whole text in all formats in just 2 seconds

Part of the pleasure of working with computers is that they can crunch massive amounts of data. The text of the Old Babylonian Letters is a piece of cake.

It takes just ten seconds to have that cake and eat it. In nearly a dozen formats.

In [37]:
A.indent(reset=True)
A.info("writing plain text of all letters in all text formats")

text = collections.defaultdict(list)

for ln in F.otype.s("line"):
    for fmt in sorted(T.formats):
        if fmt.startswith("text-"):
            text[fmt].append(T.text(ln, fmt=fmt, descend=True))

A.info("done {} formats".format(len(text)))

for fmt in sorted(text):
    print("{}\n{}\n".format(fmt, "\n".join(text[fmt][0:5])))
  0.00s writing plain text of all letters in all text formats
  1.70s done 4 formats
text-orig-full
[a-na] _{d}suen_-i-[din-nam]
qi2-bi2-[ma]
um-ma _{d}en-lil2_-sza-du-u2-ni-ma
_{d}utu_ u3 _{d}[marduk]_ a-na da-ri-a-[tim]
li-ba-al-li-t,u2-u2-ka

text-orig-plain
a-na d⁼suen-i-din-nam
qi2-bi2-ma
um-ma d⁼en-lil2-sza-du-u2-ni-ma
d⁼utu u3 d⁼marduk a-na da-ri-a-tim
li-ba-al-li-t,u2-u2-ka

text-orig-rich
a-na d⁼suen-i-din-nam
qi₂-bi₂-ma
um-ma d⁼en-lil₂-ša-du-u₂-ni-ma
d⁼utu u₃ d⁼marduk a-na da-ri-a-tim
li-ba-al-li-ṭu₂-u₂-ka

text-orig-unicode
𒀀𒈾 𒀭𒂗𒍪𒄿𒁷𒉆
𒆠𒉈𒈠
𒌝𒈠 𒀭𒂗𒆤𒊭𒁺𒌑𒉌𒈠
𒀭𒌓 𒅇 𒀭𒀫𒌓 𒀀𒈾 𒁕𒊑𒀀𒁴
𒇷𒁀𒀠𒇷𒌅𒌑𒅗

The full plain text

We write all formats to file, in your Downloads folder.

In [38]:
for fmt in T.formats:
    if fmt.startswith("text-"):
        with open(os.path.expanduser(f"~/Downloads/{fmt}.txt"), "w") as f:
            f.write("\n".join(text[fmt]))

Sections

A section in the letter corpus is a document, a face or a line. Knowledge of sections is not baked into Text-Fabric. The config feature otext.tf may specify three section levels, and tell what the corresponding node types and features are.

From that knowledge it can construct mappings from nodes to sections, e.g. from line nodes to tuples of the form:

(p-number, face specifier, line number)

You can get the section of a node as a tuple of relevant document, face, and line nodes. Or you can get it as a passage label, a string.

You can ask for the passage corresponding to the first slot of a node, or the one corresponding to the last slot.

If you are dealing with document and face nodes, you can ask to fill out the line and face parts as well.

Here are examples of getting the section that corresponds to a node and vice versa.

NB: sectionFromNode always delivers a verse specification, either from the first slot belonging to that node, or, if lastSlot, from the last slot belonging to that node.

In [39]:
someNodes = (
    F.otype.s("sign")[100000],
    F.otype.s("word")[10000],
    F.otype.s("cluster")[5000],
    F.otype.s("line")[15000],
    F.otype.s("face")[1000],
    F.otype.s("document")[500],
)
In [40]:
for n in someNodes:
    nType = F.otype.v(n)
    d = f"{n:>7} {nType}"
    first = A.sectionStrFromNode(n)
    last = A.sectionStrFromNode(n, lastSlot=True, fillup=True)
    tup = (
        T.sectionTuple(n),
        T.sectionTuple(n, lastSlot=True, fillup=True),
    )
    print(f"{d:<16} - {first:<18} {last:<18} {tup}")
 100001 sign     - P313335 obverse:8  P313335 obverse:8  ((227310, 229370, 244327), (227310, 229370, 244327))
 268163 word     - P510665 obverse:9  P510665 obverse:9  ((226821, 228295, 234114), (226821, 228295, 234114))
 208220 cluster  - P510766 obverse:9  P510766 obverse:9  ((226925, 228516, 236231), (226925, 228516, 236231))
 245788 line     - P313410 obverse:12' P313410 obverse:12' ((227376, 229516, 245788), (227376, 229516, 245788))
 228954 face     - P292765 reverse    P292765 reverse:12 ((227126, 228954), (227126, 228954, 240157))
 227169 document - P382526            P382526 left:2     ((227169,), (227169, 229057, 241107))

Clean caches

Text-Fabric pre-computes data for you, so that it can be loaded faster. If the original data is updated, Text-Fabric detects it, and will recompute that data.

But there are cases, when the algorithms of Text-Fabric have changed, without any changes in the data, that you might want to clear the cache of precomputed results.

There are two ways to do that:

  • Locate the .tf directory of your dataset, and remove all .tfx files in it. This might be a bit awkward to do, because the .tf directory is hidden on Unix-like systems.
  • Call TF.clearCache(), which does exactly the same.

It is not handy to execute the following cell all the time, that's why I have commented it out. So if you really want to clear the cache, remove the comment sign below.

In [41]:
# TF.clearCache()

Next steps

By now you have an impression how to compute around in the corpus. While this is still the beginning, I hope you already sense the power of unlimited programmatic access to all the bits and bytes in the data set.

Here are a few directions for unleashing that power.

  • display become an expert in creating pretty displays of your text structures
  • search turbo charge your hand-coding with search templates
  • exportExcel make tailor-made spreadsheets out of your results
  • share draw in other people's data and let them use yours
  • similarLines spot the similarities between lines

See the cookbook for recipes for small, concrete tasks.

CC-BY Dirk Roorda