<jupyter_start><jupyter_text>Analyzing Meridians Artblocks This notebook analyzes the Meridian Art Blocks collection with a focus on their traits and the prices they can realize. [Visit the repository](https://github.com/ymyke) for more notebooks on other projects, to use the code, or to contribute.Important: This notebook uses [Plotly](https://plotly.com/python/) mostly to visualize data. Plotly charts won't be rendered in Githubs notebook viewer. So please [view this notebook on nbviewer](https://nbviewer.org/github/ymyke/niftylytics/blob/main/meridians.ipynb) (or on your local installation after cloning the repo). Note that nbviewer caches notebooks and sometimes takes a long time to pick up a new version.On the upside, all the Plotly charts are interactive. You can zoom, hover, pan, etc. Zooming is especially important due to the outliers in some the data so you can zoom in on the interesting parts.<jupyter_code>import plotly.io as pio
pio.renderers.default = "notebook_connected"<jupyter_output><empty_output><jupyter_text>Get the Meridians Note that this takes a couple of minutes. Also note the timestamp below,that is when the information was last retrieved.<jupyter_code>import datetime
from opensea import ARTBLOCK_CONTRACT, retrieve_assets

MERIDIAN_IDS = list(range(163000000, 163001000))
assets = retrieve_assets(token_ids=MERIDIAN_IDS, contract=ARTBLOCK_CONTRACT)
print(datetime.datetime.now())<jupyter_output>.................................................. -- All 1000 assets retrieved.
2022-01-09 16:41:02.383677<jupyter_text>Build a dataframe<jupyter_code>import numpy as np
import pandas as pd
from helpers import turn_assets_into_df
df, traits = turn_assets_into_df(assets)
hoverdata = [ "Name", *traits, "Probscore"]
assert set(df.LPsymbol.unique()) == set(["ETH", "WETH", np.nan]), """
This sheet does not do currency conversion at the moment and therefore 
assumes all prices are in (W)ETH. But there are more symbols in the input 
data which would lead to apples being compared to ranges below. Aborting.
"""<jupyter_output><empty_output><jupyter_text>How many pieces have a last price on OpenSea?<jupyter_code>print(f"{df[~df.Lastprice.isna()].shape[0] / df.shape[0]:.0%}")<jupyter_output>83%<jupyter_text>How are prices evolving over time? Note that this only takes into account the last sale of each piece.<jupyter_code>import plotly.express as px
import plotly.graph_objects as go
import matplotlib.pyplot as plt

plt.rcParams["figure.figsize"] = (20, 8)
pd.plotting.register_matplotlib_converters()

fig = px.scatter(df, x="LPdate", y="Lastprice", hover_data=hoverdata)
fig.show()<jupyter_output><empty_output><jupyter_text>Price levels today<jupyter_code>fig = px.histogram(df, x="Lastprice", marginal="box", hover_data=hoverdata)
fig.show()<jupyter_output><empty_output><jupyter_text>How frequent are the different traits?<jupyter_code>for trait in traits:
    display(
        pd.DataFrame(df[trait].value_counts(normalize=False, sort=True, ascending=True)).transpose()
    )<jupyter_output><empty_output><jupyter_text>How much do people pay for the different traits?<jupyter_code>for trait in traits:

    fig = go.Figure()
    for traitvariant in (
        df.groupby(trait).median().sort_values("Lastprice", ascending=False).index
    ):
        fig.add_trace(
            go.Box(
                y=df[df[trait] == traitvariant].Lastprice.values,
                name=traitvariant,
                boxpoints="all",
                jitter=0.2,
                whiskerwidth=0.2,
                marker_size=2,
                line_width=1,
            )
        )
    fig.update_layout(title=trait)
    fig.show()<jupyter_output><empty_output><jupyter_text>**Some observations:*** Chromaticity: Highest prices for monochrome, approx. same level for duochrome and trichrome, lowest prices for polychrome.* Frequency: No clear tendencies. Low seems to be clearly performing worst in terms of price; the other three are similar, on a slightly higher level.* Palette: High variance and low sample size, therefore no clear tendencies. Peninsula seems to be performing somewhat consistently better than for example Esker or Dune.* Structure: Irrelevant wrt to price level.* Style: The most seldom styles obviously have the largest variance, and they also command the highest prices. Huge variance and outliers for Charcoal and Prime. Prismatic the style with consistently lowest prices.* Zoom: Not much differentiation.Overall, **Style** has the biggest impact on price, with additional impact added by **Chromaticity** and **Palette**. The other traits (Frequency, Structure, Zoom) don't make a lot of a difference.  Digging deeper into Palette and Chromaticity traitsDigging deeper into Palette, we find that the only traits that use the different palettes are Paletted and Prime. Thus, for most Styles, the Palette is irrelevant because there is only one:<jupyter_code>_ = pd.crosstab(df.Style, df.Palette).plot(kind="bar", stacked=True)<jupyter_output><empty_output><jupyter_text>The situation is similar, albeit a bit more nuanced with the Chromaticity trait. Many styles, espcially the seldom onces, only have one type of Chromaticity, Monochrome:<jupyter_code>_ = pd.crosstab(df.Style, df.Chromaticity).plot(kind="bar", stacked=True)<jupyter_output><empty_output><jupyter_text>Looking at those Styles that have several variants of Chromaticity, we see that Chromaticity makes some difference in certain cases, e.g., Duochrome would be the preferred Chromaticity for both Watercolour and Polarized from a pure price perspective:<jupyter_code>fig = px.box(df[df.Style.isin(["Gouache", "Paletted", "Polarized", "Procedural", "Watercolour"])], x="Style", y="Lastprice", color="Chromaticity")
fig.show()<jupyter_output><empty_output><jupyter_text>Probscore as a simple unified metric to predict pricesThe Probscore metric is simply the product of the probabilities of the different traits of a piece. There is currently not much value in that metric. While only the lowest probabilities are able to command the very high outlier prices, overall correlation between Probscore and Lastprice remains low:<jupyter_code>fig = px.scatter(df, x="Probscore", y="Lastprice")
fig.show()
df[["Probscore", "Lastprice"]].corr()<jupyter_output><empty_output><jupyter_text>Style Frequency as an improved metricSince style seems to be the most important trait of a Meridian, let's try style frequency as a measure. We'll "turn it around" here so larger numbers mean higher rarity in terms of style. Correlation is much higher than with the generic Probscore.<jupyter_code>df["Style Rarity"] = 1 / df["Style"].map(df["Style"].value_counts(normalize=True))
fig = px.scatter(df, x="Style Rarity", y="Lastprice")
fig.show()
df[["Style Rarity", "Lastprice"]].corr()<jupyter_output><empty_output><jupyter_text>The correlation with Price (i.e., asking price) is much lower, which could either meanthat would-be sellers have misguided expectations or that there is a shift happeningaway from Style being the most important trait.<jupyter_code>df[["Style Rarity", "Price"]].corr()<jupyter_output><empty_output><jupyter_text>Displaying Lastprice over time along with Style Rarity as the color encoding, we seethat there has been immediate price raises for the rarest pieces right after launch andnot so much afterwards:<jupyter_code>px.scatter(
    df,
    x="LPdate",
    y="Lastprice",
    hover_data=hoverdata,
    color="Style Rarity",
    log_y=False,
)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09777540961769016, 'fraction_numerical': 0.007592913280937791, 'mean_word_length': 5.21523178807947, 'pattern_counts': {'":': 0, '<': 59, '<?xml version=': 0, '>': 59, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '288268', 'license': 'MIT', 'n_tokens_mistral': 2293, 'n_tokens_neox': 2174, 'n_words': 775, 'path': 'meridians.ipynb', 'repo_name': 'ymyke/abalyzer', 'size': '7507'}