Merge between with timeseries in Pandas

Pandas dataframes are a great way of preserving relational database structure while gaining a huge API of vectorized functions for cleaning and analysis. However, I’ve always been frustrated by a lack of merge between functionality in pandas. So, I’ve come up with a fairly simple method for returning a merge-between-like result set with good runtime and manageable memory usage, that’s also easy to parallelize.

Using SQL, I can easily perform a left join between two datasets on a between condition, that associates my left table key with all matching data from the right table. For example:

ON b.timestamp >= a.start_time
    AND b.timestamp <= a.end_time

will return all the rows in b that have timestamps between the start and end time for each row in a. Admittedly, it can be very slow, but it’s a useful process. As of pandas .2, there’s no simple way to perform this operation.

Pandas .2 does have merge_asof implemented, which matches a to the nearest entry on b in a specified direction. But for many data analysis applications, I don’t want the closest entry, I want all the entries in between. For example, I might want to take the mean over a time period, or do a more sophisticated aggregation. Or, maybe my timestamps aren’t perfectly aligned, so I’d rather pull in a range of data for each entry in a and perform an estimation from the values in b. Either way, merge_asof doesn’t cut it.

If all the start and end times in a are distinct (no overlapping events), then I can easily interpolate the data in a and b by concatenating, sorting on the timestamp, and boolean masking for data that falls between a the same start and end, though the runtime isn’t very good. I don’t want to go through the extra data cleaning steps to make my data distinct — which may not be the right approach anyways. Instead, I’m going to use a little iterating and some fancy indexing to return an efficient merge-between-like result set. It’s also naturally suited to parallelization, since I can do the operations in distinct chunks (like splitting time series data by date), group by a unique identifier from my left table, and return a condensed result set. Since the condensed result set is at most len(left table), and python clears memory between function calls, I can also manage memory usage by intelligent chunking, and process more data than I can load into working memory at any one time.

Let’s create some random data using numpy’s methods for sampling with replacement. In this example, I have a website selling merchandise, and three different ad campaigns promoting my website. The ad campaigns are running on multiple channels, and have different spot lengths. I want to know if during the ad spots, there’s any notable activity on my website.

Let’s say my website sells socks, shirts, pants, backpacks and pencils, and records the time when a user either clicks, adds an item to their cart, or purchases it. This is clearly a bit cleaner than any data you’ll ever find in the wild, but gets the job done.
For my advertisement data, I’ll assume my ads have three different sets of content, and can be 30, 45, or 60 seconds long. Each ad will also have a unique integer id.

import pandas as pd
import numpy as np

PRODUCTS = ['socks', 'shirt', 'pants', 'backpack', 'pencil']
ACTION_LIST = ['click', 'add_to_cart', 'purchase']
HOURS = [9, 10, 11, 12, 13]
AD_LENGTHS = [30, 45, 60]
AD_CONTENT = ['foo', 'bar', 'baz']

You can use pandas functions to create a series with times, but I want to demonstrate the functionality using timeseries data without any assumptions of regularity.
First, this means that I can use data of arbitrary length and sparsity/density. Many real-world time series data sets are naturally extremely long, with some dense and some sparse periods. Resampling (as much of the pandas documentation recommends) is extremely slow on long data sets. If some of my data is nanoseconds apart, and some of my data is hours apart, resampling to nanoseconds will create an absurdly huge dataframe. Resampling to minutes combines my denser data, and I want to preserve its granularity.
Second, this approach works on time series data after minimal data cleaning. A website might have multiple users performing actions at the same time, so duplicates are valid. I may be running two ads on different channels that overlap with each other — also valid.
The following generates two dataframes, one with my website information, and one with my advertisement information. The website dataframe has 10,000 rows, each representing an action on a product, and the ad dataframe has 200 rows, representing a unique advertisement than ran on 1/1/2017. The website dataframe is sorted by timestamp, and has a timestamp index.

# create a dataframe of random actions at random times
website_actions = pd.DataFrame(data={'product':np.random.choice(PRODUCTS, 10000),
                                     'action':np.random.choice(ACTION_LIST, 10000),
                                     'hour':np.random.choice(HOURS, 10000),
# all on the same date
website_actions['year'] = 2017
website_actions['month'] = 1
website_actions['day'] = 1
# coalesce all datetime columns into one timestamp
website_actions['timestamp'] = pd.to_datetime(website_actions[['year', 'month', 'day', 'hour', 'minute', 'second']])
# clean up the df and create a time index
website_actions.drop(['year', 'month', 'day', 'hour', 'minute', 'second'], axis=1, inplace=True)
website_actions = website_actions.sort_values('timestamp').set_index('timestamp')

# do the same thing for ad spots
advertisements = pd.DataFrame(data={'content':np.random.choice(AD_CONTENT, 200),
                                    'hour':np.random.choice(HOURS, 200),
                                    'minute':np.random.choice(60, 200),
                                    'second':np.random.choice(60, 200),
                                    'ad_length':np.random.choice(AD_LENGTHS, 200),
advertisements['year'] = 2017
advertisements['month'] = 1
advertisements['day'] = 1
advertisements['start_time'] = pd.to_datetime(advertisements[['year', 'month', 'day', 'hour', 'minute', 'second']])
advertisements['end_time'] = advertisements['start_time'] + pd.to_timedelta(advertisements['ad_length'], unit='s')
advertisements.drop(['year', 'month', 'day', 'hour', 'minute', 'second'], axis=1, inplace=True)

I try to never iterate over dataframes (or use apply), but in this case, it’s necessary, since a given website row can correspond to arbitrarily many ads and vice versa. For each ad, we can index into the website dataframe between the start and end times to find the actions during the ad. If we label each slice with the unique ad id, we can simply concatenate all the slices together and return all the actions during each ad, labeled by the ad id.

ad_actions = []
# pass over the dataframe. Itertuples is the most efficient method, and we don't need an index
for tup in advertisements.itertuples(index=False):
    # use the advertisement start and end times to index into the website df
    site_slice = website_actions[tup.start_time:tup.end_time]
    # label the slice
    site_slice['id'] =
    # add it to the list of slices
# when we're done iterating, concatenate all the slices into our final dataframe
ad_actions = pd.concat(ad_actions)

As mentioned above, an advantage of this method is that you can define your own split-apply-combine functions to use on the chunks of data. Since we preserved the unique ad id for each slice of website data, we can group on the id and easily merge our final results back into the advertisement dataframe to do further aggregations.

One simple analysis task might be to determine which ad content generated the most revenue. Let’s say we make $1 for every pair of socks we sell, $2.50 for every shirt, $3.25 for every pair of pants, and $2.75 for every backpack, and we lose $.05 for every pencil we sell. One way of looking at the effectiveness of an ad is comparing to what we could have sold to what we did sell. We could have sold anything that appears in our website dataframe, since that represents items people clicked on, items people added to their carts (but didn’t checkout), and final sales. We sold anything that has action = ‘purchase’.

# define product revenues
revenue_by_prod = {'socks':1, 'shirt':2.5, 'pants':3.25, 'backpack':2.75, 'pencil':-.05}
# every row has potential revenue
ad_actions['potential_revenue'] = ad_actions['product'].replace(revenue_by_prod)
# but it only has real revenue if it was purchased
ad_actions['real_revenue'] = np.where(ad_actions['action'] == 'purchase', ad_actions['potential_revenue'], 0)

# compute the real and potential revenue by ad
revenue_by_ad = ad_actions.groupby('id')[['potential_revenue', 'real_revenue']].sum()
# missed is potential - real
revenue_by_ad['missed_revenue'] = revenue_by_ad['potential_revenue'] - revenue_by_ad['real_revenue']
# print the worst 5 ads by missed revenue
print(revenue_by_ad.sort_values('missed_revenue', ascending=False).head())

# merge the revenue back into the advertisement df on the id column
advertisements = pd.merge(advertisements, revenue_by_ad, left_on=['id'], right_index=True)
# now aggregate by content
revenue_by_content = advertisements.groupby('content')[['missed_revenue']].sum()

Since the values are randomly generated, every run will produce a different result.

I see:

content     missed_revenue
'bar'       2311.05
'baz'       2069.90
'foo'       2005.45

Not the most sophisticated analysis! For example, I haven’t taken into account the way content may have overlapped during ad time. I also don’t know if ‘baz’ content missed the least revenue because people declined to buy the most pencils. I can add a little more granularity to my results by counting the pencils per ad:

pencils_by_ad = ad_actions.groupby(['product', 'id']).size().loc['pencil'].to_frame().rename(columns={0:'num_pencils'})

Or using a bit of fancy indexing, the unbought pencils per ad:

unbought_pencils_by_ad = ad_actions.groupby(['product', 'action', 'id']).size().loc[
    ('pencil', 'click'), ('pencil', 'add_to_cart')].reset_index().groupby('id')[[0]].sum().rename(
    columns={0: 'num_unbought_pencils'})

And then seeing which content has the most unbought pencils:

tot_pencils = pencils_by_ad.join(unbought_pencils_by_ad)
advertisements = pd.merge(advertisements, tot_pencils, left_on='id', right_index=True)
print(advertisements.groupby('content')[['num_pencils', 'num_unbought_pencils']].sum())

Which gives me:

content     num_pencils num_unbought_pencils
'bar'       383         222.0
'baz'       310         206.0
'foo'       339         204.0

It doesn’t look like any set of content is correlated with whether or not site visitors look at or purchase pencils (which makes sense, since the numpy sampling functions sample from a constant distribution by default, though you can seed them however you like).

Creating open source weaving software – an introduction

From fabric durability and density to color and pattern to material and scale, many technical and creative factors come into play when planning a weaving project. All woven fabric comes from an interplay of warp (vertical) and weft (horizontal) threads, but given an empty loom and a pile of yarn, how do weavers anticipate that interlacement? Though some parts of weaving happen either by happy accident or through creative freedom, other parts, like threading a loom to make a stable cloth, are fully specified and locked into place before any weaving can begin. Weavers use drafts in order to plan their projects and understand the underlying structures of their textiles. Though it’s fully possible to draft a project on paper, most weavers use weaving software, such as pixel loom, in their designing and debugging process.

Three sample drafts: Plain weave on two harnesses, the simplest weave structure; point-on-point twill on four harnesses; and houndstooth, a color and weave effect structure

Drafting Basics

Drafts use a standardized visual system to convey all information necessary for creating a textile. They specify the number of harnesses needed, the placement of warp threads, the relationship between harnesses and pedals, the pattern of the final textile, and allow the weaver to infer technical details of the final cloth, like sturdiness and balance between front and back. While drafting, a weaver can experiment with different combinations of colors and raised and lowered threads in order to explore the possibilities of their threading or make modifications. During drafting, weavers can also compare multiple thread arrangements side by side in order to understand how threading choices will change the final cloth. Weavers create drafts to see their planned textile before committing to weaving, to share their projects with other weavers, and to understand different weave structures.

Drafts have four main components:

  1. Threading – assigning warp threads to harnesses. By convention, the bottom row of the threading is the first harness on the loom. Any loom has a fixed number of harnesses – from two to hundreds. In the drafts above, the threading is the top box of numbers.
  2. Treadling – Specifying which threads to raise when inserting a weft thread. The weaver must raise at least one treadle per pick (inserted thread, also called a shot), to tack that thread down. The weaver can’t raise all treadles in one shot for the same reason – the weft thread would pass under every warp thread and not weave into the final cloth. In the drafts above, the threading is the rightmost column of numbers, but the treadling can go on the left or the right.
  3. Drawdown – The combination of the threading and the treadling. If I have a thread on harness 1 and raise harness 1, then the weft thread will pass under that warp thread, and the warp thread will be visible. If I don’t raise harness 1, then the weft thread will pass over the warp thread, and the weft thread will be visible. In the first two drafts above, the warp threads are black and the weft threads are white. (Conventionally, if no colors are specified, the draft is shown in black and white.) In the houndstooth draft, the grey areas of the warp and weft show that those threads are black, and the other threads are white. The structure is a simple twill, but the resulting fabric has a pattern called a color and weave effect.
  4. Tie up – not necessary, but generally included in complicated patterns or when the weaver is using a floor loom. For simple looms or table looms, the weaver generally manually lifts the desired harnesses directly. For example, if they want to raise harnesses 1 and 2, they would press on pedals one and two simultaneously, and can combine harnesses at will. For complicated structures, a weaver might have to raise harnesses 1, 3, 5, 6, and 8 — which isn’t feasible with foot pedals. So, the weaver can attach harnesses 1, 3, 5, 6, and 8 to pedal 1, and press one pedal to raise 5 harnesses. Many weaving patterns have a repetitive treadling sequence, so the tie up specifies an efficient way of tying the pedals to the harnesses that includes all necessary harness combinations for the pattern. Looms have a fixed number of pedals, so not all tie ups are possible on all looms.

On the loom diagram below:

Source: Wikimedia

When dressing the loom, the threading tells the weaver to pass each thread through a distinct heddle eye (8 on the diagram), attached to one of the numbered harnesses (7).

The tie up describes how to tie the peddles (16) to the harnesses (7), and the draw down shows the final cloth (11).

At each row in the treadling, the weaver raises and lowers the appropriate harnesses, lifting some threads, and creating space for a weft thread to pass through (10).

Drafting GUIs

There are a number of available drafting programs available, each providing basic functionality to manually insert warp and weft threads to display a drawdown.


Beyond accurately displaying which threads are raised and lowered per pick, there are a few more basic functions that make weaving software useful. Once the weaver has designed their textile, the weaving software generally allows them to export the image as a .wif file (a standardized text format), print the threading and treadling, aggregate the number of threads on each harness, and so forth.

Ideally, weaving software would be able to reverse-draft an image: given a pixelated design, compute what the theoretical threading and treadling would be. Even better weaving software would allow a user to free draw or upload an image, turn that image into a draft, and then iteratively reduce the image to meet the weaver’s loom requirements.

Stay tuned!



Adapting colonial coverlets to 8-harness looms

Bright and graphic, tiling natural and geometric forms, American colonial coverlets form a dazzling body of early American textile art. These impressive blankets and coverings were woven in the mid-19th century on complicated jacquard looms, combining cotton and wool threads in sharp, contrasting colors. Without access to jacquard looms, handweavers may want to replicate these designs, but lack the technology to do so. This post explains the methodology for adapting profile drafts of colonial coverlets for eight-shaft floor looms with no special added technology.

Though a system for algorithmically generating profile drafts from schematized images of weaving is on my programming docket, I haven’t gotten there yet, so this post relies on profile drafts from The Coverlet Book, by Helene Bress. This two-volume compendium translates over 2,000 colonial coverlets into drawdowns, with ample discussion, and I highly recommend it as a very technical coffee table book. Natalie Boyett at the Chicago Weaving School  taught me not only how to read and adapt profile drafts, and about overshot and double weave, but also everything I know about weaving. Everything in this post was developed with Natalie’s help, guidance, and expertise.

I chose a tree and snowflake design from Bress’ text described as “neat, bold, and classic” (Bress 282). The original coverlet was woven in double weave with five blocks over 20 harnesses, in dark blue wool and off-white cotton. It was woven in two pieces, which were joined together to create a blanket of 66 1/2 x 82 1/4 in., with 1 in. of attached fringe on the vertical sides and 1 in. of natural fringe on the bottom. The coverlet is labeled as T12524, drawdown D69, on page 282 of volume 2, for those following along.

Photo of original coverlet and drawdown, reproduced from Bress:

The complete profile draft gives the threading and treadling for vertical and horizontal pine trees, an intermediary, half-tone tree/crosshatch section, and the interior circular pattern. To simplify the adaptation, I’ve reproduced a small section of the draft showing only the horizontal trees to the far left:

Original profile draft

Rather than showing the draft thread by thread, a profile draft simplifies the weaving into its constituent blocks, which can be realized with a number of weave structures. So while the coverlet’s original loom may have had many, many more harnesses than the now-typical four or eight, as long as the block draft has eight or fewer blocks, we can adapt it to an eight-shaft loom. (A profile draft with four or fewer blocks works on a four-shaft loom, but most coverlets have more complex designs.)

In a simple overshot structure, tabby and pattern shots alternate. Each block requires 2 harnesses, but the each block shares harnesses with its adjacent blocks, so overshot in four blocks takes four harnesses. Additionally, odd and even harnesses must alternate. An example threading:

Block A – 1,2
Block B – 2,3
Block C – 3,4
Block D – 4,1
Plain weave – 1,3; 2,4

(Algebraists  – many structures in weaving are based on the cyclic group!)

The original coverlet uses five blocks. Unfortunately, in the overshot family, five blocks don’t work, because Block E would be threaded on 5,1 — an odd and an odd. We can simply add in a block F to give Block E on 5,6, Block F on 6,1. How do you decide where block F goes? In the original block draft, there are many areas where the blocks ascend consecutively, most clearly in the tree trunk areas. Because of the tie up, Blocks E and F always weave together, so adding in Block F at the start of an extended length of block E doesn’t alter the original design:

This is a subsection of the altered profile draft:Altered profile draft

The design is slightly longer, because the ascending sections of blocks are now one block longer. If that difference matters in the final design, it’s easy enough to remove Block E from the tree trunk, or eliminate the decorative geometric pattern at the far left side. Since the trees are a border element that contrasts with the central rounded square design, I don’t mind the elongation.

Overshot weaves create patterns by passing weft threads over blocks of warp threads. The resulting sections of warp threads are called floats. Floats appear as strong, unbroken blocks of solid pattern color. The number of consecutive iterations of a block in the draft determines the length of the float.

This image shows the drawdown woven with white tabby and blue pattern. The grey squares show where pattern blocks are tied down. Areas without vertical divisions are floating. Because overshot alternates pattern and plain weave, each shot is reinforced with plain weave, so floats occur horizontally only:block draft with floats

The original coverlets don’t have any floats, because they weren’t woven in overshot (hence, many more harnesses needed). Though floats are an easy way to make compelling patterns, the hanging threads are a significant structural defect if the final textile will be used extensively. Long floats catch, snag, and can break. The floats shown above are too long for practical use as a blanket or coverlet without lining on the back.

We can solve this structural problem by introducing another weave structural. Double weave is a weave structure that weaves simultaneously on the front and back sections which the weaver can join, separate, and reverse at will, allowing tubes, pockets, or combinations. Colonial double weave is a related weave structure where a particular threading allows the weaver to weave double sided, solid cloth where the front and back reverse each other. By adapting double weave threading to traditional overshot, we tack down floats while maintaining the solid pattern, half tone, and solid plain blocks created by overshot.

Colonial double weave uses two contrasting colors for the warp, a front color and a back color. The warp is wound together so front and back (traditionally, and in this post, light and dark) alternate. Each dent in the reed gets one light and one dark thread, which will weave reversed patterns on top of each other. The paired threads go into different harnesses according to a simple rule: each harness has a corresponding opposite, and for each light thread in the draft, we subsequently put the dark thread on its corresponding opposite.

Each harness’s opposite is self + \frac{\text{num blocks}}{2}. On a four-block draft, that gives 1 \leftrightarrow 3 and 2 \leftrightarrow 4. On a six-block draft, that gives 1 \leftrightarrow 42 \leftrightarrow 5, and 3 \leftrightarrow 6.

Our original thread by thread draft becomes the threading for the light threads. We then add and interleave dark warp threads to that threading according to the relationships above:

double weave threading

Because each pair of light and dark threads are sleyed in the same dent of the reed, the total woven width remains the same, though the thickness and overall structure significantly changes.

Traditionally colonial double weave uses four harnesses and a tromp as writ treadling on the light threads with a paired treading for dark threads (treadling follows the threading, so the sequence of light threads 1, 2, 3, 4 in the pattern gives treadling: 1 light, 124 dark, 2 light, 213 dark, 3 light, 324 dark, 4 light, 413 dark, etc). We don’t want to treadle following the threading, because we have a specific pattern in mind — we want to weave our block pattern at will. This doesn’t work with a true colonial double weave, but it doesn’t actually matter – the colonial double weave threading is enough to tack down pattern floats. By pairing light and dark threads on opposite harnesses, when we lower harnesses 1, 3, 4, 5 and 6 to to weave pattern in Block E on the pesky tree trunk section, our dark threads on harness 2 raise, so the pattern float weaves under warp at every other dent in the reed for its entire length. Sett at 12 ends per inch (EPI), that caps float lengths at 1/6 in — sturdy enough to function as a coverlet. As long as the dark threads in the warp match the pattern weft color, there’s very little stylistic interference.

Here’s a sample, with some commentary:


This sample uses white and forest green cotton rug warp with thin white cotton tabby and thick green wool pattern weft. Based on the thickness of the pattern and tabby weft, you can experiment with the number of shots of pattern. The bottom 2/3 uses two shots of pattern for each shot indicated on the draft. In the top 1/3, and final version, I’ll stick to one shot of pattern for each shot indicated which approximates plain weave. Since I was using up bobbins, there are actually three substantially different shades of pattern weft. The bottom 1/3 is dark forest green, the middle 1/3 is bright teal, and the top 1/3 is navy blue. Because of the density of the warp, all three colors show as forest green, making this structure a neat combination of warp-facing and weft-facing characteristics.

So, if you too would like to weave a colonial coverlet but find yourself with merely an eight-shaft floor loom to work with:

  1. Adjust the block draft to an even number of blocks.
  2. Translate blocks into an overshot thread-by-thread draft.
  3. Add reverse threads to the original overshot threading.
  4. Warp in light and dark, with dark threads matching/coordinating with pattern weft.
  5. Sley the reed with two threads per dent sett for a dense plain weave (12-15 epi).
  6. Thread alternating light and dark.
  7. Weave with thin tabby and thick pattern threads with the same treadling you’d used for normal overshot.

Convincing (?) Art Historical Text Generation

It’s a commonly held prejudice that analyses of art are arbitrary; you can look at a piece of art, repeat any stock expression, and your audience, no matter how educated, will buy it. So when prompted to address contemporary art in some way, I decided to test that theory by submitting ten pages of randomly generated art historical text. Producing text with a Markov model raises questions about the role of contemporary art scholarship. On the most naïve level, it is a humorous exercise in seeing the actual words that art historians and critics produce. On a deeper level, however, the amount it convinces us of its veracity or potential veracity provokes two kinds of thoughts. The first, critical in nature, prompts inquiry into the extent that art criticism is derivative. If this text, which is by construction the shuffling and reprinting of previous texts convinces us to some degree, then we must wonder how “original” art historical knowledge production differs in scope. (Though one likely had this thought before reading any algorithmic output.)

The second thought is reflexive in nature: the position with which we naively approach such text is itself a meaningful metric for how we conceive of writing on contemporary art, and perhaps contemporary art itself. Though obviously silly, the model itself contains a vast amount of information, available immediately with the correct query. As such, it is not inherently dismissible; it knows more about contemporary writing on contemporary art than (arguably) any person, though it may produce nonsense. The position it provokes in its readers (scientific interest, disdain, amusement) is immediately communicative of their baseline thought on contemporary art criticism, and its underlying relationship to the art itself.

Contemporary Art

For example, two chairs in a structure of racial inequality. Is there one? To withdraw from the East River to the general culture. But, if so, you could argue that the internet is based on solidarity, on the refusal to be privatized is our bodies. Yet if police responses to Occupy encampments around the United States; it is arguably the most complicated form is metaphysical guilt: for Jaspers, guilt in the Saturday Night Live skit, The Nerds, though. The look is borderline nerdy. I remembered pantywaist, a word that comes after Art. He has spoken of the exhibition Air de Paris, Paris; Pilar Corrias, London; and Esther Schipper Gallery, Berlin, Germany. On 23 October, his solo show at 47 Canal; New York. No less than a quarter of the participating artists are likely to see. After receiving a Golden Bear at the time, were really talking about Brazilian identity. Cristina Freire: In the case of the philosophy of Deleuze, but clearly favors differentiation over repetition. He offers a sketch or a norm implicit in a book. The carbon chains, O-H3, C-H3, C-OH, gradually penetrate my epidermis and travel through the deep conservatism in neo-formalist work being created by artists much younger than her ever questionable inclusions and exclusions arise, oddball juxtapositions distract.


Contemporary art text generation is based on a statistical model called a Markov Chain, a simple but powerful system used to model structures as diverse as the google search algorithm, grammar, the stock market, board games such as monopoly, weather, and insurance pricing. Markov models are based on three basic assumptions:

  1. There are finitely many states the system can occupy.
  2. There exists a probability distribution on the system that gives the conditional probability of moving from one state to another.
  3. At each time step, the future is conditionally independent of the past.

The final assumption is what makes a Markov model meaningfully different from other ways of modeling multi-state systems. Let’s assume for the sake of didactic clarity that we want to understand how two games, blackjack and snakes and ladders, work. I can do better at blackjack if I can remember the previous cards which have been dealt. Since the dealer goes through the deck incrementally, as he or she puts down cards, the probabilities of winning at each hand change. In snakes and ladders, at each turn, my position on the board and my dice roll completely determine the probability of any outcome. Since previous dice rolls do not affect the probability of future dice rolls (i.e. they are statistically independent), and the only thing previously determined is my position on the board, each turn in snakes and ladders is conditionally independent of previous turns. Snakes and ladders can be Markov modelled; blackjack cannot.

For text generation, Markov models use the assumption that for each word (or fixed-length phrase, confusingly also sometimes called a “word”), there is a probability distribution on all the words that can follow it, which are further assumed to be finite. For example, if I say the word, “MoMA,” I might follow it with, “exhibition,” “curator,” “is,” “galleries,” etc., and each distinct word has a fixed probability of occurring. (The probability of a nonsense phrase like, “MoMA steamroller” will have probability at or close to zero of occurring.) Each pair of words is called a prefix-suffix pair. To generate text, then, we can pick any prefix we want, then randomly choose one of the suffixes that follows it, weighted with its probability, and choose one of the suffixes that follows that word, and so on. At each word, the words that follow the prefix depend only on the prefix – the future is conditionally independent of the past.

Implementation Details

The text generator was coded from scratch based on text scraped from online sources, also from scratch. I programmed the crawler and scraper in python using urllib to make http connections and BeautifulSoup, a screen-scraping library based in XML that translates web pages into trees. (I originally used these packages for a CS problem set). Based on the source code of the website, my code algorithmically generates the archival page urls which contain the urls for individual articles, then visits each article url and pulls the text, writing it to a text file. The model is built by analyzing the individual source text files.My program read the entirety of Jerry Saltz’s critical work for NYMag (about 200 articles), the entirety of e-flux (about 850 articles) and the 100 most recent issues of Frieze (about 6000 articles).


In order to generate text, we first need to build the probability distribution; the data used is called the corpus. The larger and more diverse the corpus, the better the text. For the sake of grammatical clarity, I fixed the prefix and suffix lengths to two words. This produced 275668 unique prefixes, each mapping to at least one suffix. Because the program needs a seed word to begin generation, I also compiled a list of 325 unique prefixes which begin articles. (Random example: “Separations are”.)  To control length and give a stronger impression of coherency, I generated text by paragraph, ending with the last period before a newline break in the original text. Though longer paragraphs give a higher probability of having a diverse range of topics, short paragraphs have surprising diversity. This paragraph was produced with a base length of 10 words:

Why do we think it’s from smoking or the pollution, because it’s too much energy and exist only in a contemporary living room, she did spice it with his kisses. Last autumn, when I was about finding ways to buy it.

The paragraph began with “Why do”, the 99th item in the list of starting words. That prefix has five possible suffixes: “I have”, “we reject”, “a lot”, “America and”, and “we think.” “we think,” the algorithm’s choice of suffix, maps to 12 suffixes, but “it’s from” only produces “smoking or.” Generation randomly oscillates between prefixes with widely varied suffixes and those with very limited options. Every paragraph in “Contemporary Art” was produced in this way, with a base length of 75 words.

To see the importance of a large corpus, we can compare this text to a Jerry Saltz bot. Generation with a base length of 20 words produced, “Jaccuse museums of bullshit! Of bogusly turning themselves into smash-hit consumer circuses, box-office sensations of voyeurism and hipster showbiz. This year, the Dependent debuted in a car accident in 1977, shortly before her 41st birthday. Morton combined conceptualism, postminimalism, magic, Americana, and kitsch with a feminist twist and a comedians wit.” Though equally coherent, this text is inherently restricted to Saltz’s syntax; its ability to produce diverse content is limited. (It should be noted, however, that each of the corpuses – Saltz, Frieze, and e-flux – are large enough to produce coherent text on their own.) As with all data projects, the quality of the text is limited by the quality of the data.

So, did it work?

Somewhat. Markov models, though straightforward to implement, generally don’t give the most life-like text as sentences have a tendency to meander and bare little relation to each other. In the future, I want to experiment with methods for connecting sentences to each other, perhaps by subdividing the corpus into content areas. It seems like NLP/NLG can become arbitrarily challenging, so it would be interesting to see how deep one has to go in exchange for a payoff in readability.

I submitted two documents: “Contemporary Art,” a ten-page randomly generated paper; and “Methodology.” According to my professor, “methodology sounded boring,” so he immediately read the paper and felt it was comparable to what a “bad art student” would “try to pass off as art history” but not really recognizable as an art historian’s paper. Thanks Matthew Jesse Jackson for being a supportive guinea pig and signing off on the project in lieu of actual, you know, art history.


Looking Closely at Olmec Style

How do we understand art from societies with no writing? What if that art is abstract?

This is a condensed and edited version of my BA thesis, “Smooth Sides and Shining Surfaces: Looking Closely at Olmec Style.” It won the Robert and Joan Feitler Prize for Best Art History BA and was presented at the UChicago Undergraduate Research Symposium, Spring 2014, in the “Design and Change” panel. Many thanks to Claudia Brittenham for editing and advising.

Three thousand years ago, in the modern state of Veracruz, Mexico, people living near a spring in the Coatzacoalcos basin began making offerings in the water. Now known as Olmec, they offered wooden sculptures, foodstuffs, stone and ceramic bowls, rubber balls, human remains, and jade and greenstone objects in dense patterns. Despite the complexity of the deposits, neither the people of El Manati nor other Olmec left decipherable writing in the course of the civilization, from 1500-300 B.C. Only a single piece of Olmec epigraphy survives at all. At a loss to understand how the Olmec understood their material culture, generations of scholars turned to the most legible, iconographic pieces in the archaeological record, using them as 1proxies for absent writing. Though sometimes productive, this methodology left vast swathes of material unstudied, functionally ignored as anti-epigraphic, anti-iconic, and therefore apparently uncommunicative. In a push to understand what objects show and what they are, we can easily lose sight of the artistic challenges built into them, encoded in apparently simple objects. In order to understand ancient cultures, we need to challenge ourselves to look closely at objects which are hard to interpret, beginning with objects which seem like there is nothing to be interpreted.

Two classes of objects, small jadeite sculptures known as celts, and white, rounded bowls known as tecomates, are ideal candidates for such study. Extremely minimal in form and design, without modeled or incised decorations, they largely occur as archaeological footnotes.1 Treating celts and tecomates as merely simple misses a source of depth, complexity, and connectedness in Olmec material culture. Even on the surface, celts and tecomates have a sophisticated decorative program that harnesses light, shadow, glow, and materiality to individuate each item, despite overall standardization. By evoking their natural sources and facture, they access a tactility which moves beyond a simple ability to be held into and interpretive process. This physical activation melds with an intellectual process of engagement where the viewer naturally associates celts and tecomates with other objects, but is constantly aware of their innate, self-contained abstraction. Their commonalities argue for a way of understanding material evidence that moves past categorizations based on theorized symbolism and instead looks deeply at the logic of the objects themselves, revealing pieces which remain self-referential and abstract.

Though at first glance these objects appear simple, closer investigation reveals their careful composition, pointing to an important position in Olmec material culture. A cluster of jadeite celts from El Manati pair unity and 2individualization, complexity and simplicity. Each celt varies the elongated, trapezoidal shape of an ancient ax head, altering the sweep of the sides and curve of the base and tip to individualized proportions. They range in color from beige to mottled grey to sea foam to slate, each piece with its own grain and variations. Whereas the top left celt has a fine, sandy white grain, the rightmost celt has a diagonally oriented, swirling white grain which grades into dark, rusty clouds near the bottom. These variations in color give the illusion of undulating weights, changes in light and shadow imitated in jade. Their curving, mirror-bright finish creates shifting pockets of lightness and depth.

Similarly, tecomates’ burnished surfaces create an artificial sense of shading in which the pot appears to glow from within, elevating them beyond simple white bowls. Tecomates contrast monochromatic slip with a lustrous, 3mirror-burnished finish.2 Creamy and shining on the exterior, black and matte on the interior: a white-slipped tecomates from the Valley of Mexico glows, harnessing undulating light and shadow. Subtle variations in the raw materials, temperature flows during firing, and fuel types create changes in fluctuations in the white color palette, a subtle compositional detail that enhances the burnished surface’s optical effects. At the widest part of the pot, the brighter beige slip grades into smoky grey clouds, set off with spots of sienna that bleed into darker grey. These irregularities carefully mimic the undulating effect of the lustrous slip. Far from randomly chosen or carelessly executed, celts and tecomates demonstrate a closely planned composition that justifies rigorous art historical consideration.

Though celts and tecomates are formally complex, their minimal composition’s position in Olmec material culture is poorly understood, leading to their canonical designation as simplified representations of other objects. Treated as skeuomorphs, their form becomes incidental and inherited. In general, an object is a skeuomorph or skeuomorphic if it replicates on material in another: for example, pottery that looks like basketry or metal, or stone that looks like textile. For want of clear imagery, round, white tecomates become ceramic copies of gourds; green, tapered celts imitate maize cobs, ax heads, or the axis mundi.3 Categorizing any object as a skeuomorph treats its meaning reductively, where an object is little more than a copy – perhaps with humorous or clever connotations – but in the end, based entirely on some outside source. Its attributes are inherited, not original, undercutting its integrity. Presupposing this relationship denies an object meaning in its own right.

These abstract objects’ lack of decoration makes iconographic classifications problematic, undercutting the effectiveness of skeuomorphism as an interpretive framework. Though objects gain canonical associations, the actual relationship between a skeuomorph and its referent is rarely explicated, as if minimalistic, white bowl is somehow naturally a gourd. The categorization circumvents significant methodological problems related to motivation, which is challenging if not impossible to reconstruct without written evidence. Further difficulties arise when presumed skeuomorphs lose decorative elements present in the originals. As anthropologist Stephen Houston writes, “Most skeuomorphs have two […] tangled features, the first that cross-media transfer is usually presumed to be unidirectional, the second that transfers often mix media or juxtapose elements in notable bricolage.”4 If a celt is more minimal in shape, surface, and texture than an ear of maize, it actually borrows very little from a literal ear of corn. Imposing this iconographic scheme a priori allows the desire to find symbolism to overcome the possibility that undecorated objects may represent nothing other than themselves.

Indeed, the relationships between celts and maize and tecomates and gourds are more impressionistic than definite, underscoring these problems. Scholars generally associate tecomates with gourds in part because of their unusual shape; most Olmec vessels are wider edged plates and dishes. Later Maya paintings show figures using rounded gourd vessels, with the same deep curves as a tecomates. However, since all Olmec gourds decomposed long ago, tecomates remain in an ambiguous position, as we have no idea what Olmec gourd cups really looked like, or if gourd cups were a cheaper imitation of rounded ceramics.5 Similarly, greenstone celts vary widely in shape, size, and color; simply put, some celts look plausibly like maize, and others do not. A unidirectional relationship between original and imitation is far from evident.

With their simplified forms and carefully produced surfaces, celts and tecomates refer to both natural source material and other related forms, engaging in multiple conceptual gestures. Sculpted from boulders tumbled through the Motagua River, celts bear a formal resemblance to their 5unworked source material, carrying a reference to an idealized natural object. As large boulders of jadeite tumble through fast-moving rivers, softer stone chips off, leaving roughly triangular forms known as cobbles.6 These cobbles are often naturally celtiform, with a wide, rounded end, and a noticeably pointed tip. Though celts reduce cobbles’ scale, they maintain and distill cobbles’ inherent triangular composition. Symmetry replaces natural variability with respect to form. The regulation of these natural asymmetries imposes an inherent sense of a human hand in an object that recalls its material source.

With a more amorphous material, tecomates do not have the same formal relationship to clay that celts have to jade cobbles. Nonetheless, their surface treatment foregrounds a luminosity unique to burnished clay, producing an object that first and foremost looks like pottery rather than an imitation of gourds. The Olmec produced many ceramic objects which painstakingly reproduce natural forms far more complex than gourds: ducks, babies, and jaguar figures, to name only a few. Therefore, by editing tecomates down to the simplest possible form, Olmec sculptors made a definite choice to not faithfully imitate gourds. Furthermore, highly burnished pottery has a surface unique among Mesoamerican materials. Its gentle diffusion of light and seemingly internally generated luminosity make any burnished pot look distinctly unlike other objects. It is not imitative of other natural surfaces, including gourds. By foregrounding their material as an essential aspect of their appearance, celts and tecomates are always bound to a fundamentally sculptural gesture which resists a representative reading. Rather than imitating another object, celts and tecomates maintain a continual tension between gourd-like and clay-like, corn-like and jade-like, problematizing their reduction to simple symbols.

Without recourse to symbolism, theorizing these objects requires a return to form and composition, drawing the pieces into a dialogue with a viewer or user. Via a manipulation of scale and shape, celts and tecomates encourage usage that mimics their facture, creating meaning that is self-referential rather than outward looking. The final piece engages the viewer both visibly and tangibly. A tecomate’s smooth curves and delicate size match the shape of an open palm. Spanning only 4 ¾ inches across and 3 ½ inches vertically, with a flattened bottom and low curves, the tecomate pictured above would nestle exactly into a slightly open palm, with the thumb and fingers brushing the sides.7 On contact the highly burnished walls are silky smooth to the touch. This attention to personal scale, coupled with the undulating patterns of light and shadow on the surface, underscores that tecomates communicate primarily as abstract objects, not iconographically.

Both objects evoke their facture in form through their elongated, trapezoidal shape, creating a self-referential dialogue with the viewer. True jadeite was the hardest material known in Mesoamerica; creating celts was an extended and intensive process.8 Initial blocks were separated from boulders with percussive strokes, then chipped into a rough version of the final form. With repeated grinding from an abrasive such as jade or quartz sand with a wooden or stone saw, rope, or drill, a simplified, trapezoidal form emerged. 9 In the case of ceramic, since Mesoamericans did not have pottery wheels, tecomates were assembled from a flat base and coils of clay shaved into a smooth curve. Repeated scrapings with a stone or smooth implement, over the days required to harden from leather-hard to the firing stage, created the final lustrous and smooth surface. In both objects, once the first rough form emerges, the two follow parallel processes of reduction, first in large chunks of material, and then in a slow, painstaking smoothing and shaping. These artistic manipulations create shining, luminous surfaces that the naturally holds and touches.

Stroking motions naturally are firmest at the point of contact, smoothly decreasing in pressure until the hand lifts away. This gesture congeals in celts and tecomates’ elegantly tapering and curving forms. In this sense, they connect the current feel of their surface with the previous touchings that created their surface and form. Their form becomes tactile through its solidification of a physical process. Tactility is therefore interpretive: a viewer recognizes it in a form, and understands it as part of a process. Moreover, this interpretation is bidirectional. Responding physically to a piece prompts a to wonder about the hands the made it, and understanding the process of shaping a piece calls out for the replication of that process via touch. This self-reference operates entirely outside of iconography.

Tactility, viewer engagement, and self-referentiality come to the forefront in an expanded view of Olmec material culture, demonstrating the theoretical possibilities of frameworks that operate without reference to iconography. A large burial site at Tlatilco, in the Valley of Mexico, contains hundreds of 6graves, each with a mixture of pottery, bones, musical instruments, figurines, among other objects.10 Ceramics rest in every grave.11 Beyond simple ubiquity, however, the pots nestle into bodies, with other sculptural objects at a greater distance. Graves show pots between legs and tucked under arms, with figurines set apart by as much as a foot, a relatively large distance in a small burial. Though graves do not perfectly demonstrate how living Olmec related to their pots, they do encode significant indicators of social practices.12 Such depositions gesture at the importance of tactility and touch in Mesoamerican contexts.

Though semiotic interpretations provide an easy starting point for schematizing ancient materials, they either exclude abstract objects as unreadable, or attempt to impose iconography where it cannot fit. Both methodologies deny sophisticated, conceptual artworks autonomy in their own right. They tacitly position ancient, abstract art as less realized, unable to produce its meaning independently. Despite this historiography, minimal Olmec objects do afford closer readings that reveal complex connections between the pieces, their facture, and their viewer. Importantly, these connections extend beyond close readings of individual objects, using abstract pieces as a new theoretical framework for ancient art.


Harlow, George E. “Middle American Jade: Geologic and Petrologic Perspectives on Variability and Source.” In Precolumbian Jade: new geological and cultural interpretations. Edited by Frederick W. Lange. Salt Lake City: University of Utah Press. 1993.

Houston, Stephen. The Life Within: Classic Maya and the Matter of Permanence. New Haven: Yale University Press, 2014.

Joyce, Rosemary A. “Social Dimensions of Pre-Classic Burials.” In Social Patterns in Pre-Classic Mesoamerica. Edited by David C. Grove and Rosemary A. Joyce. Washington, DC: Dumbarton Oaks Research and Library Collection, 1999.

Taube, Karl. Olmec Art at Dumbarton Oaks. Washington, D.C.: Dumbarton Oaks Research Library and Collection. 2004.

Wu, Hung. Monumentality in Early Chinese Art and Architecture. Stanford: Stanford University Press, 1995.