<jupyter_start><jupyter_text>Lin et al. data Paper: _Evidence of general economic principles of bargaining and trade from 2,000 classroom experiments_ (https://www.nature.com/articles/s41562-020-0916-8)<jupyter_code>df = pd.read_stata('Data/market.dta')
df = df.replace('', np.nan, regex=True)<jupyter_output><empty_output><jupyter_text>Remarks:- ```SessionID``` redundant as it is encoded in ```MarketID```;- ```MarketID_period``` redundant as it encodes ```period``` and ```MarketID```.<jupyter_code>(df['SessionID'] == df['MarketID'].apply(lambda mid: int(mid/1000))).all()
(df['MarketID_period'] == (df['period'] + df['MarketID']*100)).all()
df.drop('MarketID_period', axis=1, inplace=True)
int_col(df)<jupyter_output><empty_output><jupyter_text>The data is a concatenation of five datasets storing information on the **sessions**, the **markets**, the **players**, the **order book**, and the **deals**.  Extract relevant information  Session profiles Remarks:- ```group``` is redundant as it can be extracted from ```MarketID``` and ```SessionID```<jupyter_code>(df['group'] == df['MarketID'] - df['SessionID']*1000).all()
df_sessionProfile = df[['SessionID', 'location_label', 'type_label', 'fund_label', 'class_label', 'time_label']].copy()
df_sessionProfile.drop_duplicates(inplace=True)<jupyter_output><empty_output><jupyter_text>Sanity check: no row contains ```NaN```s<jupyter_code>df_sessionProfile[df_sessionProfile.isna().any(axis=1)]
df_sessionProfile
df.drop(['SessionID', 'group', 'location_label', 'type_label', 'fund_label', 'class_label', 'time_label'], axis=1, inplace=True)<jupyter_output><empty_output><jupyter_text>Market profiles<jupyter_code>df_marketProfile = df[['MarketID', 'eq_price', 'eq_quantity', 'optimalconsumersurplus', 'optimalproducersurplus',
                       'optimal_surplus', 'Number_players', 'default', 'mean_experience_robot', 'mean_experience_nonrobot',
                       'Quantity', 'ConsumerSurplus', 'ProducerSurplus', 'surplus', 'efficiency']].copy()
df_marketProfile.drop_duplicates(inplace=True)<jupyter_output><empty_output><jupyter_text>Sanity check: no row contains ```NaN```s<jupyter_code>df_marketProfile[df_marketProfile.isna().any(axis=1)]
int_col(df_marketProfile)
df_marketProfile
df.drop(['eq_price', 'eq_quantity', 'optimalconsumersurplus', 'optimalproducersurplus', 'optimal_surplus',
         'Number_players', 'default', 'mean_experience_robot', 'mean_experience_nonrobot',
         'Quantity', 'ConsumerSurplus', 'ProducerSurplus', 'surplus', 'efficiency'], axis=1, inplace=True)<jupyter_output><empty_output><jupyter_text>Player profiles Remarks:- ```userid ...``` stores (globally) unique user IDs;- ```player(id) ...``` stores IDs which are unique within session-periods.WLOG, we will only use ```userid ...```.Moreover, ```... _profile``` stores the IDs of _all_ users recruited for the game (who did not necessarily participate in it). They can be found in ```df_playerProfile``` below. Sanity check:- the set of ```userid```s/```player```s is indeed a subset of the set of ```userid_profile```s/```playerid_profile```s;- the number of ```userid (...)```s matches the number of ```player(id) (...)```s.<jupyter_code>for mid, df_market in df.groupby(['MarketID']):
    if not set(df_market['userid'].dropna()).issubset(set(df_market['userid_profile'].dropna())):
        print('userids not a subset in market {}'.format(mid))
        
    if not set(df_market['player'].dropna()).issubset(set(df_market['playerid_profile'].dropna())):
        print('playerids not a subset in market {}'.format(mid))
        
    if len(set(df_market['userid_profile'].dropna())) != len(set(df_market['playerid_profile'].dropna())):
        print('the number of userid_profiles does not match the number of playerid_profiles in market {}'.format(mid))
        
for (mid, period), df_mp in df.groupby(['MarketID', 'period']):
    if len(set(df_mp['userid'].dropna())) != len(set(df_mp['player'].dropna())):
        print('the number of userids does not match the number of players in market {} in period {}'.format(mid, period))<jupyter_output><empty_output><jupyter_text>Remark: there seems to be a mismatch—while ```Number_players``` (which can be found in ```df_marketProfile```) mostly equals the number of players in ```df_playerProfile```, it can also be smaller.<jupyter_code>compare_nplayers = {
    'marketProfile_vs_orderBook': [0,0,0],
    'marketProfile_vs_playerProfile': [0,0,0]
    }

for mid, df_market in df.groupby(['MarketID']):
    nplayers_marketProfile = df_marketProfile[df_marketProfile['MarketID'] == mid]['Number_players'].iloc[0]    
    nplayers_orderBook = len(set(df_market['userid'].dropna()))
    nplayers_playerProfile = len(set(df_market['userid_profile'].dropna()))
    
    if nplayers_marketProfile < nplayers_orderBook:
        compare_nplayers['marketProfile_vs_orderBook'][0] += 1
    elif nplayers_marketProfile == nplayers_orderBook:
        compare_nplayers['marketProfile_vs_orderBook'][1] += 1
    else:
        compare_nplayers['marketProfile_vs_orderBook'][2] += 1

    if nplayers_marketProfile < nplayers_playerProfile:
        compare_nplayers['marketProfile_vs_playerProfile'][0] += 1
    elif nplayers_marketProfile == nplayers_playerProfile:
        compare_nplayers['marketProfile_vs_playerProfile'][1] += 1
    else:
        compare_nplayers['marketProfile_vs_playerProfile'][2] += 1

compare_nplayers
df_playerProfile = df[['MarketID', 'period', 'userid_profile', 'role', 'payoff', 'valuecost']].drop_duplicates()
df_playerProfile.dropna(inplace=True, how='all')
df_playerProfile = df_playerProfile[~df_playerProfile.isnull().any(axis=1)]
df_playerProfile['valuecost'] = df_playerProfile['valuecost'].apply(lambda v: tuple(int(x) for x in v.split('-')))
df_playerProfile['lenval'] = df_playerProfile['valuecost'].apply(len)
df_playerProfile<jupyter_output><empty_output><jupyter_text>Remark: players' market roles and/or valuation(s) are not necessarily constant across periods<jupyter_code>nonConst_side = 0
nonConst_val = 0

for (mid, uid), df_player in df_playerProfile.groupby(['MarketID', 'userid_profile']):
    if len(set(df_player['role'])) > 1:
        nonConst_side += 1
    
    if len(set(df_player['valuecost'])) > 1:
        nonConst_val += 1
        
nonConst_side
nonConst_val<jupyter_output><empty_output><jupyter_text>Order book & deals<jupyter_code>df = df[df['role'].isna()].copy()
df.drop(columns=['userid_profile', 'playerid_profile', 'role', 'payoff', 'valuecost'], axis=1, inplace=True)
df_deals = df[df['player'].isna()].dropna(axis=1, how='all')
df_orderBook = df[~df['player'].isna()].dropna(axis=1, how='all')
int_col(df_orderBook)
int_col(df_deals)
df_orderBook
df_deals<jupyter_output><empty_output><jupyter_text>Restructure the data<jupyter_code>df_orderBook.drop('player', axis=1, inplace=True)
df_orderBook.rename({
    'MarketID': 'treatment',
    'period': 'round',
    'userid': 'id',
    'playerrole': 'side',
    'offer_time': 'time',
    'transactedprice': 'price',
    'bidask': 'bid'
    }, axis=1, inplace=True)
df_orderBook<jupyter_output><empty_output><jupyter_text>Remarks: there is a one-to-one correspondence between ```buysell``` and ```side```<jupyter_code>(df_orderBook.apply(lambda row: row['side'][0:3] == row['buysell'][0:3], axis=1)).all()
df_orderBook.drop('buysell', axis=1, inplace=True)
df_orderBook = df_orderBook[['treatment', 'round', 'time', 'id', 'side', 'bid', 'price', 'unit', 'orderid', 'status']]
df_orderBook.replace('--', np.nan, inplace=True)
df_orderBook<jupyter_output><empty_output><jupyter_text>Add valuations to the order book **Is the length of the valuation tuple constant across all periods of a market and is it the same for all players?**<jupyter_code>valLengths = df_playerProfile.groupby(['MarketID'])['lenval']

print('Markets with varying valuation tuple lengths')
valLengths.nunique()[valLengths.nunique() != 1]

violatingPlayers = []

for market in valLengths.nunique()[valLengths.nunique() != 1].index:
    df_market = df_playerProfile[df_playerProfile['MarketID'] == market]
    df_market
    
    df_market['lenval'].value_counts()
    violatingPlayers.extend((market, player) for player in df_market[df_market['lenval'] != df_market['lenval'].median()]['userid_profile'])
violatingPlayers<jupyter_output><empty_output><jupyter_text>It seems likely that these players were mistakenly assigned too many items.<jupyter_code>for market, idx in violatingPlayers:
    print('Player {} in market {}'.format(idx, market))
    df_playerProfile[(df_playerProfile['userid_profile'] == idx) & (df_playerProfile['MarketID'] == market)]
    df_orderBook[(df_orderBook['id'] == idx) & (df_orderBook['treatment'] == market)]
    df_deals[((df_deals['buyeruserid'] == idx) | (df_deals['selleruserid'] == idx)) & (df_deals['MarketID'] == market)]
    print('\n')<jupyter_output>Player 47400.0 in market 246001<jupyter_text>None of these players made more deals than others did; hence, their valuation tuples can be truncated so that their lengths match the rest.<jupyter_code>for market, idx in violatingPlayers:
    df_market = df_playerProfile[(df_playerProfile['MarketID'] == market)]
    
    standard_length = int(df_market['lenval'].median())
    
    _filter = (df_playerProfile['userid_profile'] == idx) & (df_playerProfile['MarketID'] == market)

    df_playerProfile.loc[_filter, 'valuecost'] = df_playerProfile.loc[_filter, 'valuecost'].apply(lambda x: x[0:standard_length])
    df_playerProfile.loc[_filter, 'lenval'] = standard_length
for market, idx in violatingPlayers:
    df_playerProfile[(df_playerProfile['userid_profile'] == idx) & (df_playerProfile['MarketID'] == market)]<jupyter_output><empty_output><jupyter_text>The lengths of the valuation tuples are now constant across all periods and all players within a single market (uniquely determined by ```MarketID```).<jupyter_code>updateBids = pd.merge(df_orderBook, df_deals,  how='outer', left_on=['treatment', 'round', 'orderid'], right_on = ['MarketID','period', 'bidorderid'])
updateAsks = pd.merge(df_orderBook, df_deals,  how='outer', left_on=['treatment', 'round', 'orderid'], right_on = ['MarketID','period', 'sellorderid'])

df_orderBook = updateBids.combine_first(updateAsks)
df_orderBook
len(df_orderBook[df_orderBook['bid'] == 'MARKET ORDER'])
df_orderBook['bid'] = df_orderBook['bid'].replace({'MARKET ORDER': -1})
df_orderBook['bid'] = df_orderBook['bid'].astype(float)
int_col(df_orderBook)
df_orderBook['price_x'] = df_orderBook['price_x'].astype(float)
df_orderBook['price_y'] = df_orderBook['price_y'].astype(float)
df_orderBook.drop(['transactionid', 'sellorderid', 'bidorderid', 'MarketID', 'period', 'sideinitiated'], axis=1, inplace=True)
df_orderBook
def update_valuation(row):
    if row['side'] == 'Buyer':
        val = row['buyervalue']
    else:
        val = row['sellercost']
        
    return val

def update_match_id(row):
    if row['side'] == 'Buyer':
        match_id = row['selleruserid']
    else:
        match_id = row['buyeruserid']
        
    return match_id
   
df_orderBook['valuation'] = df_orderBook.apply(update_valuation, axis=1)
df_orderBook['match_id'] = df_orderBook.apply(update_match_id, axis=1)
df_orderBook.drop(['selleruserid', 'buyeruserid', 'sellercost', 'buyervalue'], axis=1, inplace=True)
df_orderBook.rename({'time_y': 'match_time'}, axis=1, inplace=True)<jupyter_output><empty_output><jupyter_text>"MARKET ORDER" bids—which correspond to players accepting deals—still need to be accounted for (these are now stored as -1 in the ```bid``` column). For now, set them to ```NaN``` and add "MARKET ORDER" to the status.<jupyter_code>def marketOrder_rows(row):
    if row['bid'] < 0:
        return '{}, MARKET ORDER'.format(row['status'])
    else:
        return row['status']

df_orderBook['status'] = df_orderBook.apply(marketOrder_rows, axis=1)
df_orderBook['bid'] = df_orderBook['bid'].replace({-1: np.nan})
df_orderBook[df_orderBook['bid'].isna()]<jupyter_output><empty_output><jupyter_text>According to the order book, 10 players seem to have made one too many deal (see below). However, since these deals were not tracked in the deals table, the erroneous entries in the order book can be corrected easily.<jupyter_code>missingPriceInformation = df_orderBook[~(df_orderBook['price_x'].eq(df_orderBook['price_y']) | (df_orderBook['price_x'].isna() & df_orderBook['price_y'].isna()))]
missingPriceInformation
for idx, row in missingPriceInformation.iterrows():
    print('Market {}, round {}, player {}'.format(row['treatment'], row['round'], row['id']))
    
    _filter = (df_playerProfile['MarketID'] == row['treatment']) & (df_playerProfile['period'] == row['round']) & (df_playerProfile['userid_profile'] == row['id'])
    
    lenVal = df_playerProfile[_filter]['lenval'].iloc[0]
    
    print('Length of valuation tuple: {}'.format(lenVal))
    
    _filter = (df_orderBook['treatment'] == row['treatment']) & (df_orderBook['round'] == row['round']) & (df_orderBook['id'] == row['id'])   
    
    print('Number of deals in the order book: {}'.format(df_orderBook[_filter]['price_x'].count()))
    print('Number of deals in the table with deals: {}'.format(df_orderBook[_filter]['price_y'].count()))    
    
    df_orderBook[_filter]
for idx, row in missingPriceInformation.iterrows():
    df_orderBook.loc[idx, 'price_x'] = np.nan
df_orderBook.loc[missingPriceInformation.index]
df_orderBook.rename({'time_x': 'time', 'price_x': 'price'}, axis=1, inplace=1)
df_orderBook.drop(['orderid', 'price_y'], axis=1, inplace=True)
df_orderBook = df_orderBook[['treatment', 'round', 'time', 'id',
                             'side', 'valuation', 'bid', 'price', 
                             'match_id', 'match_time', 'unit', 'status']]
df_orderBook<jupyter_output><empty_output><jupyter_text>Fill in the missing valuations<jupyter_code>df_orderBook.loc[:,'valuation'] = df_orderBook.groupby(['treatment', 'round', 'id'])['valuation'].fillna(method='bfill')
for (treatment, rnd, idx), dfi in df_orderBook.groupby(['treatment', 'round', 'id']):
    if dfi['valuation'].isnull().any():       
        nvals = dfi['valuation'].nunique()
        valuations, lenval = df_playerProfile[(df_playerProfile['MarketID'] == treatment) & (df_playerProfile['period'] == rnd) & (df_playerProfile['userid_profile'] == idx)][['valuecost','lenval']].iloc[0]
        
        df_orderBook.loc[dfi.index,'valuation'] = df_orderBook.loc[dfi.index,'valuation'].fillna(valuations[min(nvals, lenval-1)])<jupyter_output><empty_output><jupyter_text>If a bid is missing and was generated by accepting a deal, set its value to the deal price<jupyter_code>df_orderBook['bid'] = df_orderBook['bid'].fillna(df_orderBook['price'])<jupyter_output><empty_output><jupyter_text>Store the processed data<jupyter_code>df_orderBook.to_csv('Data/Lin_orderBook_processed.csv', index=False)
df_orderBook
df_sessionProfile.to_csv('Data/Lin_sessionProfile_processed.csv', index=False)
df_marketProfile.to_csv('Data/Lin_marketProfile_processed.csv', index=False)
df_playerProfile.to_csv('Data/Lin_playerProfile_processed.csv', index=False)
df_deals.to_csv('Data/Lin_deals_processed.csv', index=False)<jupyter_output><empty_output><jupyter_text>Implement Market \& Player classes<jupyter_code>class Market():
    def __init__(self, df):
        self.df = df
        
        self.load_data()
        
    def __repr__(self):
        return 'Market {}'.format(self.market_id)
    
    def load_data(self):
        self.market_id = self.df['treatment'].iloc[0]
        self.n_rounds = max(self.df['round'])
              
        self.players = []
        
        for idx, dfi in self.df.groupby('id'):
            player = Player(self, dfi)
            self.players.append(player)
            
        self.n_items = self.get_n_items()
            
    def get_n_players(self):        
        return len(self.players)
    
    def get_n_rounds(self):        
        return self.n_rounds
    
    def get_players(self):
        return self.players
    
    def get_n_items(self):
        #return self.df.groupby(['round', 'id'])['price'].count().max()
        return df_playerProfile[df_playerProfile['MarketID'] == self.market_id]['lenval'].max()
    
    def is_singleItem(self):
        n_items = self.get_n_items()
        
        if (n_items == 1):
            return True
        else:
            return False
        
    def is_singlePeriod(self):
        if (self.n_rounds == 1):
            return True
        else:
            return False
        
    def switches_sides(self):
        switches_sides = False
        
        for player in self.players:
            if player.switches_sides():
                switches_sides = True
                break
                
        return switches_sides
    
    def switches_valuations(self):
        switches_valuations = False
        
        for player in self.players:
            if player.switches_valuations():
                switches_valuations = True
                break
        
        return switches_valuations
    
    def fixed_attributes(self):
        if self.switches_sides() or self.switches_valuations():
            return False
        else:
            return True
        
class Player():
    def __init__(self, market, df):
        self.df = df
        self.market = market
        self.update()
        
    def __repr__(self):
        return 'Player {} in market {}'.format(self.player_id, self.market)
        
    def update(self):
        self.player_id = self.df['id'].iloc[0]
        self.sides = {}
        self.valuations = {}
        self.df_profile = df_playerProfile[
            (df_playerProfile['MarketID'] == self.market.market_id)
            & (df_playerProfile['userid_profile'] == self.player_id)
        ]
        
        for rnd, dfr in self.df_profile.groupby('period'):
            self.sides[rnd] = dfr['role'].iloc[0]
            self.valuations[rnd] = dfr['valuecost'].iloc[0]
    
    def switches_sides(self):
        if self.df['side'].nunique() > 1:
            return True
        else:
            return False
        
    def switches_valuations(self):
        if len(set(self.valuations.values())) > 1:
            return True
        else:
            return False
markets = []

for treatment, dft in df_orderBook.groupby(['treatment']):
    markets.append(Market(dft))<jupyter_output><empty_output><jupyter_text>Analysis **How many _different_ markets did the players participate in?**<jupyter_code>n_markets = df_playerProfile.groupby('userid_profile')['MarketID'].nunique()

_ = plt.hist(n_markets)
_ = plt.show()

n_markets.describe()

print('Number of players in at least 6 different experimental sessions: {}'.format(sum(n_markets > 5)))<jupyter_output><empty_output><jupyter_text>**Within markets, how many periods did the players participate in? How many different valuation tuples were they assigned within each market?**<jupyter_code>n_periods = df_playerProfile.groupby(['userid_profile', 'MarketID'])['period'].nunique()
n_valuationTuples = df_playerProfile.groupby(['userid_profile', 'MarketID'])['valuecost'].nunique()

_ = sns.jointplot(x=n_periods, y=n_valuationTuples, kind='reg')
_ = plt.xlabel('Number of periods')
_ = plt.ylabel('Number of different valuation tuples')
_ = plt.show()<jupyter_output><empty_output><jupyter_text>Number of different valuation tuples assigned to the participants<jupyter_code>n_sides = df_playerProfile.groupby(['userid_profile', 'MarketID'])['role'].nunique()
n_sides.describe()<jupyter_output><empty_output><jupyter_text>**How many items did a subject trade with within a single trading period?**<jupyter_code>market_lenVals = df_playerProfile[['MarketID', 'lenval']].drop_duplicates()['lenval']

_ = plt.hist(market_lenVals)
_ = plt.show()

market_lenVals.value_counts()

market_lenVals.describe()<jupyter_output><empty_output><jupyter_text>Single-item double auctions An average market involves trading with three goods; overall, there are **302 single-item** markets (note that the players' reservation prices and/or market sides need not be the same across the rounds).<jupyter_code>singleItem_markets = df_playerProfile[df_playerProfile['lenval'] == 1]['MarketID'].drop_duplicates().tolist()<jupyter_output><empty_output><jupyter_text>**161** of these markets are single-period, **141** are multi-period.Assume that if a round was not recorded in the order book, no bids/asks were submitted. Accordingly, a market with bids and asks submitted only from round 2 onward is assumed to be multi-period.<jupyter_code>n_periods = df_playerProfile[df_playerProfile['MarketID'].isin(singleItem_markets)].groupby('MarketID')['period'].max()

_ = plt.hist(n_periods)
n_periods.describe()
n_periods.value_counts()<jupyter_output><empty_output><jupyter_text>Multi-period single-item double auctions<jupyter_code>groups = df_playerProfile[df_playerProfile['MarketID'].isin(singleItem_markets)].groupby('MarketID')
multiPeriod_singleItem_markets = groups.filter(lambda group: max(group['period']) > 1)<jupyter_output><empty_output><jupyter_text>Do valuations change across these?<jupyter_code>groups = multiPeriod_singleItem_markets.groupby(['MarketID', 'userid_profile'])['valuecost'].nunique()
groups = groups.reset_index()
groups = groups.rename({'valuecost': 'Number of different valuation tuples'}, axis=1)
groups<jupyter_output><empty_output><jupyter_text>Find markets with constant valuations across all rounds, i.e., markets where the maximum number of different valuation tuples per player amounts to 1.<jupyter_code>nvalsMarkets = groups.groupby('MarketID')['Number of different valuation tuples'].max()
_ = plt.hist(nvalsMarkets)
nvalsMarkets.value_counts()
nvalsMarkets.value_counts()<jupyter_output><empty_output><jupyter_text>**There are 96 single-item multi-period double auctions with constant valuations.**<jupyter_code>constVal_multiPeriod_singleItem_markets = nvalsMarkets[nvalsMarkets == 1].index.tolist()<jupyter_output><empty_output><jupyter_text>Number of periods in these markets<jupyter_code>df_playerProfile[df_playerProfile['MarketID'].isin(constVal_multiPeriod_singleItem_markets)].groupby(['MarketID'])['period'].max().value_counts()<jupyter_output><empty_output><jupyter_text>Number of players in these markets (as originally planned); recall that some participants took part in several experimental markets<jupyter_code>df_playerProfile[df_playerProfile['MarketID'].isin(constVal_multiPeriod_singleItem_markets)].groupby('MarketID')['userid_profile'].nunique().value_counts().sort_index().rename_axis('Number of players').reset_index(name='Market counts')<jupyter_output><empty_output><jupyter_text>Losses **Percentage of bids/asks which would incur a loss to the player if they resulted in a deal**<jupyter_code>df_B = df_orderBook[df_orderBook['side'] == 'Buyer'].dropna(subset=['bid'])
df_S = df_orderBook[df_orderBook['side'] == 'Seller'].dropna(subset=['bid'])

demand_B = df_B.apply(lambda row: row['valuation'] - row['bid'], axis=1)
demand_S = df_S.apply(lambda row: row['bid'] - row['valuation'], axis=1)
len(demand_B[demand_B < 0])/len(demand_B)*100
len(demand_S[demand_S < 0])/len(demand_S)*100<jupyter_output><empty_output><jupyter_text>Single-item double auctions with fixed market sides and valuations<jupyter_code>singleItem = [m.market_id for m in markets if (m.is_singleItem() == True) & (m.fixed_attributes() == True)]
df_single = df_orderBook[df_orderBook['treatment'].isin(singleItem)]
df_singleB = df_single[(df_single['side'] == 'Buyer')].dropna(subset=['bid'])
df_singleS = df_single[(df_single['side'] == 'Seller')].dropna(subset=['bid'])

demand_B = df_singleB.apply(lambda row: row['valuation'] - row['bid'], axis=1)
demand_S = df_singleS.apply(lambda row: row['bid'] - row['valuation'], axis=1)
len(demand_B[demand_B < 0])/len(demand_B)*100
len(demand_S[demand_S < 0])/len(demand_S)*100<jupyter_output><empty_output><jupyter_text>**Percentage of bids/asks resulting in a deal which incurred loss to the buyer/seller**<jupyter_code>df_B = df_orderBook[df_orderBook['side'] == 'Buyer'].dropna(subset=['bid','price'])
df_S = df_orderBook[df_orderBook['side'] == 'Seller'].dropna(subset=['bid', 'price'])

demand_B = df_B.apply(lambda row: row['valuation'] - row['price'], axis=1)
demand_S = df_S.apply(lambda row: row['price'] - row['valuation'], axis=1)

len(demand_B[demand_B < 0])/len(demand_B)*100
len(demand_S[demand_S < 0])/len(demand_S)*100<jupyter_output><empty_output><jupyter_text>Repeated single-item double auctions with fixed market sides and valuations<jupyter_code>df_singleB = df_single[(df_single['side'] == 'Buyer')].dropna(subset=['bid', 'price'])
df_singleS = df_single[(df_single['side'] == 'Seller')].dropna(subset=['bid', 'price'])

demand_B = df_singleB.apply(lambda row: row['valuation'] - row['price'], axis=1)
demand_S = df_singleS.apply(lambda row: row['price'] - row['valuation'], axis=1)

len(demand_B[demand_B < 0])/len(demand_B)*100
len(demand_S[demand_S < 0])/len(demand_S)*100<jupyter_output><empty_output><jupyter_text>**Percentage of deals incurring loss to the buyer/seller/both**<jupyter_code>demand = df_deals.apply(lambda row: min(row['buyervalue'] - row['price'], row['price'] - row['sellercost']), axis=1)
len(demand[demand < 0])/len(demand)*100

demand = df_deals[df_deals['MarketID'].isin(singleItem)].apply(lambda row: min(row['buyervalue'] - row['price'], row['price'] - row['sellercost']), axis=1)
len(demand[demand < 0])/len(demand)*100<jupyter_output><empty_output><jupyter_text>Ikica et al. data versus Lin et al. data<jupyter_code>df = pd.read_csv('Data/Data.csv')<jupyter_output><empty_output><jupyter_text>Use a single label to encode the session<jupyter_code>df['treatment'] = df.apply(lambda row: '{} {}'.format(row['treatment'], row['game']), axis=1)<jupyter_output><empty_output><jupyter_text>General comparison<jupyter_code>columns = pd.MultiIndex.from_product([['Lin et al.'], ['All', 'Single-item with fixed market roles and valuations']], names=['Data', 'DAs'])
index = pd.Index(range(1,6), name='Number of rounds')

df_comparison = pd.DataFrame(index=index, columns=columns)

for n_rounds in index:
    all_markets = [m for m in markets if (m.n_rounds == n_rounds)]
    all_players = []
    
    for m in all_markets:
        all_players.extend([p.player_id for p in m.players])
    
    all_players = set(all_players)
    
    df_comparison.loc[
        n_rounds,
        ('Lin et al.', 'All')
    ] = '{} ({})'.format(len(all_markets), len(all_players))

    single_markets = [m for m in all_markets if (m.is_singleItem() == True) & (m.fixed_attributes() == True)]
    single_players = []
    
    for m in single_markets:
        single_players.extend([p.player_id for p in m.players])
    
    single_players = set(single_players)
    
    df_comparison.loc[
        n_rounds,
        ('Lin et al.', 'Single-item with fixed market roles and valuations')
    ] = '{} ({})'.format(len(single_markets), len(single_players))
    
    df_comparison.loc[
        n_rounds,
        ('Ikica et al.', 'All')
    ] = '{} ({})'.format(
        len(df.groupby('treatment').filter(lambda g: max(g['round']) == n_rounds)['treatment'].drop_duplicates().index),
        df.groupby(['treatment']).filter(lambda g: max(g['round']) == n_rounds)['id'].nunique()
    )

all_markets = [m for m in markets if (m.n_rounds >= 6)]
all_players = []

for m in all_markets:
    all_players.extend([p.player_id for p in m.players])

all_players = set(all_players)
    
df_comparison.loc[
    '$\geq 6$',
    ('Lin et al.', 'All')
] = '{} ({})'.format(len(all_markets), len(all_players))

single_markets = [m for m in all_markets if (m.is_singleItem() == True) & (m.fixed_attributes() == True)]
single_players = []

for m in single_markets:
    single_players.extend([p.player_id for p in m.players])

single_players = set(single_players)

df_comparison.loc[
    '$\geq 6$',
    ('Lin et al.', 'Single-item with fixed market roles and valuations')
] = '{} ({})'.format(len(single_markets), len(single_players))

df_comparison.loc[
    '$\geq 6$',
    ('Ikica et al.', 'All')
] = '{} ({})'.format(
    len(df.groupby('treatment').filter(lambda g: max(g['round']) >= 6)['treatment'].drop_duplicates().index),
    df.groupby('treatment').filter(lambda g: max(g['round']) >= 6)['id'].nunique()
)

all_players = []

for m in markets:
    all_players.extend([p.player_id for p in m.players])
all_players = set(all_players)

df_comparison.loc['Total', ('Lin et al.', 'All')] = '{} ({})'.format(len(markets), len(all_players))

single_markets = [m for m in markets if (m.is_singleItem() == True) & (m.fixed_attributes() == True)]
single_players = []

for m in single_markets:
    single_players.extend([p.player_id for p in m.players])

single_players = set(single_players)

df_comparison.loc[
    'Total',
    ('Lin et al.', 'Single-item with fixed market roles and valuations')
] = '{} ({})'.format(len(single_markets), len(single_players))

df_comparison.loc[
    'Total',
    ('Ikica et al.', 'All')
] = '{} ({})'.format(len(df['treatment'].drop_duplicates().index), df['id'].nunique())
df_comparison
print(df_comparison.to_latex(escape=False))<jupyter_output>\begin{tabular}{llll}
\toprule
Data & \multicolumn{2}{l}{Lin et al.} & Ikica et al. \\
DAs &           All & Single-item with fixed market roles and valuations &          All \\
Number of rounds &               &                                                    &              \\
\midrule
1                &  3807 (22105) &                                         161 (1032) &        0 (0) \\
2                &   1238 (9516) &                                           25 (340) &        0 (0) \\
3                &    446 (4805) &                                           57 (662) &        0 (0) \\
4                &    232 (4141) &                                             5 (71) &        0 (0) \\
5                &      53 (940) &                                             7 (96) &        0 (0) \\
$\geq 6$         &      33 (472) &                                             2 (83) &   104 (1751) \\
Total            &  5809 (32126) &                                         257 (1917)[...]<jupyter_text>**Percentage of bids/asks resulting in a deal**<jupyter_code>print('Lin et al.')
df_orderBook['price'].count()/len(df_orderBook)

print('Ikica et al.')
df['price'].count()/len(df)<jupyter_output>Lin et al.<jupyter_text>Hypothesis testing Extract buyer and seller valuations<jupyter_code>def get_valuations(df):
    '''Extract buyer and seller valuations.'''
    valuations = {}
    
    for side, df_side in df.groupby(['side']):
        side_valuations = df_side[['id', 'valuation']].drop_duplicates()['valuation'].tolist()
    
        if side == 'Buyer':
            side_valuations.sort(reverse=True)
        else:
            side_valuations.sort()
    
        valuations[side] = side_valuations
    
    return valuations
all_valuations = {}

for (treatment, rnd), dfr in df.groupby(['treatment', 'round']):
    all_valuations[treatment, rnd] = get_valuations(dfr)

for (mid, period), dfm in tqdm(df_playerProfile.groupby(['MarketID', 'period'])):
    buyer_valuations = []
    seller_valuations = []

    for idx, dfi in df_orderBook[(df_orderBook['treatment'] == mid) & (df_orderBook['round'] == period)].groupby('id'):
        vals = dfm[dfm['userid_profile'] == idx]['valuecost'].iloc[0]
        side = dfi['side'].iloc[0]

        if np.isnan(dfi['price'].iloc[-1]):
            nvals = min(dfi['price'].count()+1, len(vals))
        else:
            nvals = min(dfi['price'].count(), len(vals))

        if (side == 'Buyer'):
            buyer_valuations.extend(vals[:nvals])
        elif (side == 'Seller'):
            seller_valuations.extend(vals[:nvals])
    
    all_valuations[mid, period] = {'Buyer': buyer_valuations, 'Seller': seller_valuations}<jupyter_output>100%|██████████████████████████████████████████████████████████████████████████████| 9023/9023 [01:51<00:00, 80.86it/s]<jupyter_text>Extract CE ranges and gains of trade<jupyter_code>def get_equilibrium(buyer_valuations, seller_valuations):
    '''Extract the equilibrium price range, the gains of trade,
    and the equilibrium quantity corresponding to a given experimental market.'''
    
    buyer_valuations.sort(reverse=True)
    seller_valuations.sort()
    
    GOT = 0
    eq_quantity = min(len(buyer_valuations), len(seller_valuations))
    
    # Find the intersection of supply and demand curves and compute the corresponding gains of trade 
    for i in range(eq_quantity):
        if seller_valuations[i] > buyer_valuations[i]:
            eq_quantity = i
            break
        else:
            GOT += buyer_valuations[i] - seller_valuations[i]
    
    EPR = [seller_valuations[eq_quantity-1], buyer_valuations[eq_quantity-1]]
        
    return EPR, GOT, eq_quantity
CE_all = {}
GOT_all = {}

for (mid, period), dfm in tqdm(df_playerProfile.groupby(['MarketID', 'period'])):
    buyer_valuations =  all_valuations[mid, period]['Buyer']
    seller_valuations =  all_valuations[mid, period]['Seller']
    
    EPR, GOT, eq_quantity = get_equilibrium(buyer_valuations, seller_valuations)

    CE_all[mid, period] = EPR
    GOT_all[mid, period] = GOT
    
for (treatment, rnd), dft in df.groupby(['treatment', 'round']):
    buyer_valuations =  all_valuations[treatment, rnd]['Buyer']
    seller_valuations =  all_valuations[treatment, rnd]['Seller']
    
    EPR, GOT, eq_quantity = get_equilibrium(buyer_valuations, seller_valuations)

    CE_all[treatment, rnd] = EPR
    GOT_all[treatment, rnd] = GOT
def distances_to_EPR(deals, treatment, rnd):
    prices = deals['price']
    
    [epr1, epr2] = CE_all[treatment, rnd]

    distances = []
    
    for price in prices:    
        if price > epr2:
            distances.append(price - epr2)
        elif price < epr1:
            distances.append(price - epr1)
        else:
            distances.append(0)
    
    return distances

def distances_midCE(deals, treatment, rnd):
    prices = deals['price']
    
    [epr1, epr2] = CE_all[treatment, rnd]
    midpoint = np.mean([epr1, epr2])

    distances = []
    
    for price in prices:    
        distances.append((price - midpoint)/midpoint)
    
    return distances

def allocative_efficiency(deals, treatment, rnd):
    GOT = GOT_all[treatment, rnd]

    AE = (sum(deals[deals['side'] == 'Buyer']['valuation']) - sum(deals[deals['side'] == 'Seller']['valuation']))/GOT
    
    if AE > 1:
        print(treatment, rnd, AE, GOT)
        print(len(deals))
        display(deals)
    
    return AE

def Selten_index(deals, treatment, rnd, output='SI', eps1=0, eps2=0):
    prices = deals['price']
    
    [epr1, epr2] = CE_all[treatment, rnd]
    
    epr1 -= eps1
    epr2 += eps2
    
    if len(prices) > 0:
        EPR_prices = sum((epr1 <= prices) & (prices <= epr2)) # realised prices in EPR
        all_prices = prices.count() # all realised prices

        r = EPR_prices/all_prices

        width_EPR = epr2 - epr1 + 1

        valuations = all_valuations[treatment, rnd]
        
        possible_trade_pairs = [(vals, valb) for (vals, valb) in itertools.product(valuations['Seller'], valuations['Buyer']) if vals <= valb]

        nOutcomes = [valb - vals + 1 for (vals, valb) in possible_trade_pairs]
        nOutcomes = np.mean(nOutcomes)

        if width_EPR < nOutcomes:
            a = width_EPR/nOutcomes
        else:
            a = 1
        
        m = r-a

    else:
        m = np.nan
        r = np.nan
        a = np.nan
    
    if output == 'SI':
        out = m
    elif output == 'hitRate':
        out = r
    elif output == 'area':
        out = a
    elif output == 'all':
        out = m, r, a
        
    return out
def get_yvals(df, fun):
    deals = df.dropna(subset=['price'])
    all_yvals = []
    means = []
    
    # First-round opening prices
    yvals = []    
    
    for treatment, df_game in deals[deals['round'] == 1].groupby(['treatment']):
        yvals.append(fun(df_game[df_game['match_time'] == df_game['match_time'].min()], treatment, 1))

    yvals = list(flatten_list(yvals))
    all_yvals.append(yvals)
    means.append(np.nanmean(yvals))    
    
    # First-round closing prices
    yvals = []

    for treatment, df_game in deals[deals['round'] == 1].groupby(['treatment']):
        yvals.append(fun(df_game[df_game['match_time'] == df_game['match_time'].max()], treatment, 1))

    yvals = list(flatten_list(yvals))
    all_yvals.append(yvals)    
    means.append(np.nanmean(yvals))        
    
    # Rounds 2 to 5
    yvals = []

    for (treatment, rnd), df_game in deals[deals['round'].isin(range(2,6))].groupby(['treatment', 'round']):
        yvals.append(fun(df_game, treatment, rnd))

    yvals = list(flatten_list(yvals))
    all_yvals.append(yvals)    
    means.append(np.nanmean(yvals))
    
    # Rounds from 6 on
    yvals = []

    for (treatment, rnd), df_game in deals[deals['round'] >= 6].groupby(['treatment', 'round']):
        yvals.append(fun(df_game, treatment, rnd))

    yvals = list(flatten_list(yvals))
    all_yvals.append(yvals)    
    means.append(np.nanmean(yvals))
    
    return all_yvals, means

def mean_evolution(ax, means, fun, **kwargs):
    kwargs_fun = kwargs.get('extra_params', {})
            
    colour = kwargs.get('colour')
    label = kwargs.get('label')
    linewidth = kwargs.get('linewidth')
    markersize = kwargs.get('markersize')
    
    rounds = range(1, len(means)+1)
    _ = ax.plot(rounds, means, color=colour, marker='o', markersize=markersize, label='{}'.format(label), linewidth=linewidth, zorder=100)

def grid_layout(ax, fun):
    xticks = range(1,11)
    _ = ax.set_xticks(xticks)
    _ = ax.set_xticklabels(xticks, size=12)

    if (fun == distances_to_EPR) or (fun == distances_midCE):
        _ = ax.axhline(y=0, linestyle='solid', linewidth=5.0, color='darkgrey', alpha=0.8, zorder=0)

        yticks = np.arange(-0.2,0.205,0.1).round(1)
        _ = ax.set_ylim(min(yticks)-0.05,max(yticks)+0.05)

    elif fun == allocative_efficiency:
        _ = ax.axhline(y=1, linestyle='solid', linewidth=5.0, color='darkgrey', alpha=0.8, zorder=0)        
        
        yticks = np.arange(0,1.1,0.2).round(1)
        _ = ax.set_ylim(min(yticks)-0.1,max(yticks)+0.1)
    
    elif (fun == Selten_index):
        _ = ax.axhline(y=0, linestyle='solid', linewidth=5.0, color='darkgrey', alpha=0.8, zorder=0)        
        
        yticks = np.arange(-0.2,0.205,0.1).round(1)
        _ = ax.set_ylim(min(yticks)-0.05,max(yticks)+0.05)
    
    _ = ax.set_yticks(yticks)
    _ = ax.set_yticklabels(yticks, size=12)
singleItem = [m.market_id for m in markets if (m.is_singleItem() == True) & (m.fixed_attributes() == True)]
df_orderBook_SI = df_orderBook[df_orderBook['treatment'].isin(singleItem)].copy()

def demand(row):
    if (row['side'] == 'Buyer'):
        demand = row['valuation'] - row['bid']
    elif (row['side'] == 'Seller'):
        demand = row['bid'] - row['valuation']
    return demand

df_orderBook_SI['demand'] = df_orderBook_SI.apply(demand, axis=1)
df_orderBook_SI['strictVals'] = True

for (mid, rnd, mt), dfp in df_orderBook_SI.dropna(subset=['price']).groupby(['treatment', 'round', 'match_time']):
    if len(dfp) != 2:
        print('More than one deals made at time {} in round {} of market {}'.format(mt, rnd, mid))
    else:
        if (dfp['demand'] < 0).any():
            df_orderBook_SI.loc[dfp.index, 'strictVals'] = False
def Wilcoxon_oneSided(vals, alternative, M0):
    vals = [v-M0 for v in vals]
    w, p = stats.wilcoxon(vals, alternative=alternative, zero_method='pratt')
    
    return p

def Wilcoxon_plot(ax, all_yvals, alternative, M0_list, palette, title):
    _ = ax.set_title(title)
    _ = ax.axhline(0.05, linestyle='dashed', linewidth=2, color='gray')

    rounds = range(1, len(all_yvals)+1)    
    
    for idx, M0 in enumerate(M0_list):
        pvals = []

        for vals in all_yvals:
            if alternative == 'equivalence':
                p1 = Wilcoxon_oneSided(vals, 'greater', -M0)
                p2 = Wilcoxon_oneSided(vals, 'less', M0)
                p = max(p1,p2)
            else:
                p = Wilcoxon_oneSided(vals, alternative, M0)
            
            pvals.append(p)
        
        _ = ax.plot(rounds, pvals, marker='o', markersize=8, label=abs(M0), color=palette[idx])

    _ = ax.set_ylabel('$p$-value')
    _ = ax.label_outer()
    _ = ax.legend(title=r'$\varepsilon$', title_fontsize=12)
from textwrap import fill

blue_palette = sns.color_palette('Blues_r', n_colors=6) # used for plotting distance to CE
red_palette = sns.color_palette('OrRd_r', n_colors=5) # used for plotting allocative efficiency
teal_palette = sns.color_palette(['#136868', '#008282', '#1cbab7', '#48D3B7', '#68EACE', '#96F8E5']) # used for plotting Selten index

fig, ax = plt.subplots(5,3, figsize=(14.5,11), sharex=True, constrained_layout=True, gridspec_kw={'height_ratios': [1.8, 1, 1, 1, 1]})
ax = ax.flatten()

datasets = [df, df_orderBook, df_orderBook_SI, df_orderBook_SI[df_orderBook_SI['strictVals']]]
labels = ['Ikica et. al', 'Lin et. al (all)', 'Lin et. al (single-item)', 'Lin et. al (single-item, strict reservations)']

cols = [(0, distances_midCE, blue_palette, 'Relative distance to CE midpoint'),
        (1, allocative_efficiency, red_palette, 'Allocative efficiency'),
        (2, Selten_index, teal_palette, 'Predictive success index')]

# rows = [('equivalence', np.arange(0,0.21,0.05).round(2), blue_palette, r'$p$-value ($H_0 : \mathrm{M}_{d_{\mathrm{CE}}} \notin (-\varepsilon, \varepsilon)$)'),
#         ('greater', np.arange(1,0.79,-0.05).round(2), red_palette, r'$p$-value ($H_0 : \mathrm{M}_{\mathrm{AEff}} \leq \varepsilon$)'),
#         ('greater', np.arange(0.2,-0.01,-0.05).round(2), teal_palette, r'$p$-value ($H_0 : \mathrm{M}_{\mathrm{PSI}} \leq \varepsilon$)')
#         ]

rows = [('equivalence', [0, 0.02, 0.05, 0.10], blue_palette, r'$p$-value ($H_0 : \mathrm{M}_{d_{\mathrm{CE}}} \notin (-\varepsilon, \varepsilon)$)'),
        ('greater', [1, 0.9, 0.8], red_palette, r'$p$-value ($H_0 : \mathrm{M}_{\mathrm{AEff}} \leq \varepsilon$)'),
        ('greater', [0.20, 0.10, 0], teal_palette, r'$p$-value ($H_0 : \mathrm{M}_{\mathrm{PSI}} \leq \varepsilon$)')
        ]

for idx, (data, label) in enumerate(zip(datasets, labels)):
    deals = data.dropna(subset=['price'])    
    
    for (col, fun, palette, title) in cols:
        grid_layout(ax[col], fun)

        all_yvals, means = get_yvals(deals, fun)

        if (col == 1):
            loc = 'upper left'
            _label = fill(label, 30)
        else:
            loc = 'lower right'
            _label = label
        
        if (idx == 0):
            linewidth = 3
            markersize = 10
        else:
            linewidth = 1.5
            markersize = 8
        
        mean_evolution(
            ax[col], means, fun,
            **{'colour': palette[idx], 'label': _label, 'linewidth': linewidth, 'markersize': markersize}
        )
        
        leg = ax[col].legend(loc='lower right', fontsize=10, framealpha=0.8)
        _ = ax[col].set_title(bold_text(title), fontsize=14)
        
        axi = ax[3*(idx+1) + col]
        
        Wilcoxon_plot(axi, all_yvals, *rows[col])
        
        if idx > 0:
            _ = axi.set_title('')
        
        for t in ax[3*(idx+1) + col].get_legend().texts:
            t.set_text('{:.2f}'.format(float(t.get_text())))
            
    _ = ax[3*(idx+1)].set_ylabel(fill(labels[idx],30))
    
for axi in ax:
    _ = axi.set_xticks(range(1,5))
    _ = axi.set_xticklabels([r'Round 1\\opening price', r'Round 1\\closing price', r'Rounds 2--5', r'Rounds 6+'], rotation=10)

_ = plt.savefig('Figures/p_values.pdf', bbox_inches='tight', transparent=True)<jupyter_output><empty_output><jupyter_text>Convergence towards CE **Lin et al. data: CE based on the players actually present in the round**<jupyter_code>yvals, means = get_yvals(df_orderBook, distances_midCE)<jupyter_output><empty_output><jupyter_text>Round 1 opening prices<jupyter_code>np.mean(yvals[0])
np.median(yvals[0])<jupyter_output><empty_output><jupyter_text>**Lin et al. data:  CE based on the original design of the market**<jupyter_code>planned_valuations = {}

for (mid, period), dfm in tqdm(df_playerProfile.groupby(['MarketID', 'period'])):
    buyer_valuations = []
    seller_valuations = []

    for idx, dfi in dfm.groupby('userid_profile'):
        vals = dfi['valuecost'].iloc[0]
        side = dfi['role'].iloc[0]

        if (side == 'buyer'):
            buyer_valuations.extend(vals)
        elif (side == 'seller'):
            seller_valuations.extend(vals)
            
    planned_valuations[mid, period] = {'Buyer': buyer_valuations, 'Seller': seller_valuations}
CE_planned = {}
GOT_planned = {}

for (mid, period), dfm in tqdm(df_playerProfile.groupby(['MarketID', 'period'])):
    buyer_valuations =  planned_valuations[mid, period]['Buyer']
    seller_valuations =  planned_valuations[mid, period]['Seller']
    
    EPR, GOT, eq_quantity = get_equilibrium(buyer_valuations, seller_valuations)

    CE_planned[mid, period] = EPR
    GOT_planned[mid, period] = GOT
def distances_midCE_planned(deals, treatment, rnd):
    prices = deals['price']
    
    [epr1, epr2] = CE_planned[treatment, rnd]
    midpoint = np.mean([epr1, epr2])

    distances = []
    
    for price in prices:    
        distances.append((price - midpoint)/midpoint)
    
    return distances
yvals, means = get_yvals(df_orderBook, distances_midCE_planned)<jupyter_output><empty_output><jupyter_text>Round 1 opening prices<jupyter_code>np.mean(yvals[0])
np.median(yvals[0])<jupyter_output><empty_output><jupyter_text>**Ikica et al. data: CE based on the players actually present in the round**<jupyter_code>yvals, means = get_yvals(df, distances_midCE)<jupyter_output><empty_output><jupyter_text>Round 1 opening prices<jupyter_code>np.mean(yvals[0])
np.median(yvals[0])<jupyter_output><empty_output><jupyter_text>Round 1 closing prices<jupyter_code>np.mean(yvals[1])
np.median(yvals[1])<jupyter_output><empty_output><jupyter_text>Across rounds<jupyter_code>for rnd, dfr in df.groupby('round'):
    distances = []

    for mid, dfm in dfr[(dfr['price'].notna())].groupby('treatment'):
        CE = CE_all[mid, rnd]
        CE_midpoint = int(np.mean(CE))
    
        distances.extend(((dfm['price']-CE_midpoint)/CE_midpoint).values)
    
    print('Round {}'.format(rnd))
    np.mean(distances), np.median(distances)<jupyter_output>Round 1
{'fraction_non_alphanumeric': 0.15531668383110195, 'fraction_numerical': 0.013087881908685204, 'mean_word_length': 4.078339507517978, 'pattern_counts': {'":': 0, '<': 228, '<?xml version=': 0, '>': 223, 'https://': 1, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 17, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '53', 'dir': 'jupyter-structured-clean-dedup', 'id': '541171', 'license': 'MIT', 'n_tokens_mistral': 16588, 'n_tokens_neox': 15723, 'n_words': 3083, 'path': 'Comparison with Lin et al.ipynb', 'repo_name': 'ikicab/Experimental-double-auctions', 'size': '46608'}