Interactive Plots

[1]:
"""
Update Parameters Here
"""

COLLECTION_NAME = "MutantCats"
COLLECTION_TITLE = "Mutant Cats"

"""
Annotations to the sales graph can be added with the ANNOTATIONS list
    {
        "label": "annotation text",
        "date": "2022-02-05 17:00:00"
    },
"""
ANNOTATIONS = [
    {"label": "Reveal Date", "date": "2021-10-11 04:30:14"},
    {"label": "Mutant Gorillaz auction start", "date": "2021-11-01 18:58:00"},
]
[2]:
"""
data:25/02/2022
"""
# LOAD AND SETUP DATA
import pandas as pd
import plotly
import plotly.express as px
import numpy as np
import datetime

plotly.offline.init_notebook_mode()

from honestnft_utils import config

# Define rarity rank brackets
RARITY_RANKS = [50, 200, 400, 1000]  # Must be of length 4
RARITY_RANKS_COLOURS = [
    "red",
    "yellow",
    "orange",
    "green",
    "blue",
]  # Must be of length 5

# The API used to get sales data doesn't return the token used in the tx
# It's assumed the sale was in ETH, so collections that had sales with other tokens will display abnormal values in the sale price
# For this cases use MAX_PRICE to filter out those transactions
MAX_PRICE = 0  # value in ETH, Leave as 0 for no filter

# PLOT AESTHETIC PARAMETERS
DOT_SIZE = 8
DOT_OPACITY = 0.5

# Load Raw attributes and Rarity Data
try:
    RAW_ATTRIBUTES = pd.read_csv(f"{config.ATTRIBUTES_FOLDER}/{COLLECTION_NAME}.csv")
except:
    print(f"Unable to find Attributes data for {COLLECTION_NAME}")
    raise ValueError(
        f"Raw attributes not available in '{config.ATTRIBUTES_FOLDER}/{COLLECTION_NAME}.csv'"
    )

# Load Rarity Data
try:
    RARITY_DB = pd.read_csv(f"{config.RARITY_FOLDER}/{COLLECTION_NAME}_raritytools.csv")
except:
    print(f"Unable to load rarity data for {COLLECTION_NAME}")
    raise ValueError(
        f"Rarity data not available in '{config.RARITY_FOLDER}/{COLLECTION_NAME}_raritytools.csv'"
    )

# Load sales data
try:
    SALES_DATA = pd.read_csv(f"{config.SALES_DATA_FOLDER}/{COLLECTION_NAME}.csv")
except:
    print(f"Unable to load sales data for {COLLECTION_NAME}")
    raise ValueError(
        f"Sales Data not available in '{config.SALES_DATA_FOLDER}/{COLLECTION_NAME}.csv'"
    )

# dummy dataframe to add rarity rank to RAW_ATTRIBUTES
simple_rarity_db = RARITY_DB[["TOKEN_ID", "RARITY_SCORE", "Rank"]]

# add rarity rank to RAW_ATTRIBUTES
RAW_ATTRIBUTES = pd.merge(RAW_ATTRIBUTES, simple_rarity_db, on="TOKEN_ID")
RAW_ATTRIBUTES = RAW_ATTRIBUTES.astype(object).replace(
    np.nan, "None"
)  # Necessary to remove NaN values for the categorical graph

# add rarity rank to SALES_DATA
SALES_DATA = SALES_DATA.reset_index(drop=True)
RARITY_DB = RARITY_DB.reset_index(drop=True)
SALES_DATA = SALES_DATA.merge(RARITY_DB, left_on="TOKEN_ID", right_on="TOKEN_ID")

SALES_DATA["priceETH"] = SALES_DATA["price"].astype(float) / 1e18
SALES_DATA["RARITY_SCORE"] = SALES_DATA["RARITY_SCORE"].astype(float)

# Filter Sales Data for abnormal values
if MAX_PRICE != 0:
    SALES_DATA = SALES_DATA.loc[SALES_DATA["priceETH"] < MAX_PRICE]

# Create strings for rarity ranks categories
RARITY_RANKS_CAT = [
    f"≤{RARITY_RANKS[0]}",
    f"≤{RARITY_RANKS[1]}",
    f"≤{RARITY_RANKS[2]}",
    f"≤{RARITY_RANKS[3]}",
    f">{RARITY_RANKS[3]}",
]

# Color ranges
SALES_DATA["rankCategory"] = RARITY_RANKS_CAT[4]
RARITY_DB["rankCategory"] = RARITY_RANKS_CAT[4]
for i in reversed(range(len(RARITY_RANKS))):
    SALES_DATA.loc[
        SALES_DATA["Rank"] <= RARITY_RANKS[i], "rankCategory"
    ] = RARITY_RANKS_CAT[i]
    RARITY_DB.loc[
        RARITY_DB["Rank"] <= RARITY_RANKS[i], "rankCategory"
    ] = RARITY_RANKS_CAT[i]
[3]:
#
# SALE DATE vs PRICE IN ETH
#
fig = px.scatter(
    SALES_DATA,
    x="saleDate",
    y="priceETH",
    hover_data=["TOKEN_ID", "priceETH", "Rank"],
    color="rankCategory",
    color_discrete_sequence=RARITY_RANKS_COLOURS,
    category_orders={"rankCategory": RARITY_RANKS_CAT},
    title=f"<b>{COLLECTION_TITLE}</b> - Sales date vs Price<br><sup>(Low rank is more rare)</sup>",
)
fig.update_traces(
    marker=dict(size=DOT_SIZE, line=dict(width=1, color="DarkSlateGrey")),
    selector=dict(mode="markers"),
    opacity=DOT_OPACITY,
)
fig.update_layout(
    legend_title_text="Rarity Rank", xaxis_title="Sale Date", yaxis_title="ETH price"
)

if len(ANNOTATIONS) > 0:
    for note in ANNOTATIONS:
        fig.add_vline(
            # need to convert REVEAL_DATE to epoch timestamp due to a bug in plolty add_vline
            x=datetime.datetime.strptime(note["date"], "%Y-%m-%d %H:%M:%S").timestamp()
            * 1000,
            line_dash="dot",
            line_width=3,
            annotation_text=note["label"],
            annotation_font_size=12,
        )
fig.show()
[4]:
#
# TOKEN ID vs RARITY RANK (Similar to rarity.ipynb)
#
fig_rarity = px.scatter(
    RARITY_DB,
    x="TOKEN_ID",
    y="Rank",
    color="rankCategory",
    color_discrete_sequence=RARITY_RANKS_COLOURS,
    category_orders={"rankCategory": RARITY_RANKS_CAT},
    title=f"<b>{COLLECTION_TITLE}</b> - Token Id vs Rarity Rank<br><sup>(Low rank is more rare)</sup>",
)
fig_rarity.update_traces(
    marker=dict(size=DOT_SIZE, line=dict(width=1, color="DarkSlateGrey")),
    selector=dict(mode="markers"),
    opacity=DOT_OPACITY,
)
fig_rarity.update_layout(
    legend_title_text="Rarity Rank", xaxis_title="Token ID", yaxis_title="Rarity Rank"
)

fig_rarity.show()
[5]:
#
# TOKEN ID vs RARITY RANK (Numerical Rank with custom color scale)
#

fig_rarity = px.scatter(
    RARITY_DB,
    x="TOKEN_ID",
    y="Rank",
    hover_data=["Rank"],
    color="Rank",
    color_continuous_scale=[
        [0.0, "rgb(165,0,38)"],
        [0.03, "rgb(215,48,39)"],
        [0.06, "rgb(244,109,67)"],
        [0.09, "rgb(253,174,97)"],
        [0.12, "rgb(254,224,144)"],
        [0.15, "rgb(224,243,248)"],
        [1.0, "rgb(49,54,149)"],
    ],
    title=f"<b>{COLLECTION_TITLE}</b> - Token Id vs Rarity Rank<br><sup>(Low rank is more rare)</sup>",
)
fig_rarity.update_traces(
    marker=dict(size=DOT_SIZE, line=dict(width=1, color="DarkSlateGrey")),
    selector=dict(mode="markers"),
    opacity=DOT_OPACITY,
)
fig_rarity.update_layout(
    legend_title_text="Rarity Rank", xaxis_title="Token ID", yaxis_title="Rarity Rank"
)

fig_rarity.show()
[6]:
# Print available traits in this collection
TRAITS = list(
    set(list(RAW_ATTRIBUTES.columns))
    - set(["TOKEN_ID", "TOKEN_NAME", "RARITY_SCORE", "Rank"])
)
print("Available traits in this collection:")
print(TRAITS)
Available traits in this collection:
['SpecialBackground', 'CrownedMutant', 'SpecialEyewear', 'SpecialClothing', 'SpecialAccessories', 'Unnamed: 0', 'SpecialHeadwear', 'Mutant', 'XrayBackground', 'Clothing', 'Background', 'XrayMutant', 'AbstractBackground', 'CrownedAccessories', 'HoloBackground', 'Headwear', 'HoloMutant', 'Accessories', 'SpecialMutant', 'AbstractMutants', 'CrownedBackground']
[7]:
#
# TOKEN ID vs RARITY RANK BY TRAIT
#

# Select trait to use as colour category
TRAIT = "AbstractBackground"

fig_categorical = px.scatter(
    RAW_ATTRIBUTES,
    x="TOKEN_ID",
    y="Rank",
    color=f"{TRAIT}",
    title=f"<b>{COLLECTION_TITLE}</b> - Token Id vs Rarity Rank - Color by <b>{TRAIT}</b><br><sup><i>(Low rank is more rare)</i></sup>",
)
fig_categorical.update_traces(
    marker=dict(size=DOT_SIZE, line=dict(width=1, color="DarkSlateGrey")),
    selector=dict(mode="markers"),
    opacity=DOT_OPACITY,
)
fig_categorical.update_layout(
    legend_title_text="Rarity Rank", xaxis_title="Token ID", yaxis_title="Rarity Rank"
)
fig_categorical.show()
[8]:
"""
WARNING:
    This script creates 1 plot for each trait in the collection
"""

#
# TOKEN ID vs RARITY RANK BY TRAIT
#

# Get all trait categories in the collection
TRAITS = list(
    set(list(RAW_ATTRIBUTES.columns))
    - set(["TOKEN_ID", "TOKEN_NAME", "RARITY_SCORE", "Rank"])
)

for TRAIT in TRAITS:
    fig_categorical = px.scatter(
        RAW_ATTRIBUTES,
        x="TOKEN_ID",
        y="Rank",
        color=f"{TRAIT}",
        title=f"<b>{COLLECTION_TITLE}</b> - Token Id vs Rarity Rank - Color by <b>{TRAIT}</b><br><sup><i>(Low rank is more rare)</i></sup>",
    )
    fig_categorical.update_traces(
        marker=dict(size=DOT_SIZE, line=dict(width=1, color="DarkSlateGrey")),
        selector=dict(mode="markers"),
        opacity=DOT_OPACITY,
    )
    fig_categorical.update_layout(
        legend_title_text="Rarity Rank",
        xaxis_title="Token Id",
        yaxis_title="Rarity Rank",
    )
    fig_categorical.show()

TODO: Add notes to graph with link to Convex Labs twitter and HonestNFT Github