# start by loading in our typical set of packages
import numpy as np # NumPy
import pandas as pd # pandas
import seaborn as sns
import matplotlib.pyplot as plt
import sys
print(sys.version)
# use pip to install the package 'xlrd'
# need it to read the excel file below
!pip install xlrd
fnamein = 'Company Screening Report 0yr Lag.xls'
# use pandas to read the excel file. Open it if you like and check out why the parameters are set the way they are.
df_mult0 = pd.read_excel(fnamein,sheet_name='Screening',header=None,na_values=['-','NM'],skiprows=8)
# here we are making our own column names instead of the longer ones in the Excel file.
i = ['name','ex:tick','comp_type','exchange','industry','sector','marketcap','total_debt','shrout','revenue','gross_profit','sga','advertising','marketing','sales_marketing','gen_admin','rd_expense','int_expense','op_lease_int','ebitda','ebit','net_income','cash','capex','acquisitions','changeNWC','beta1','beta2','beta5','etr','tev','gRev1yr','gRev3yr','ebit_margin','ebitda_margin','gross_margin','roe','roc','altman']
# set column names
df_mult0.columns = i
# We are going to make the ticker our index - i.e. the unique identifier. So we can't have any NA values, so .dropna()
df_mult0.dropna(axis=0, how='any', subset=['ex:tick'],inplace=True)
df_mult0.set_index('ex:tick',inplace=True, verify_integrity=True)
print(df_mult0.info())
df_mult0.describe()
# fill in the two variables mentioned above to see their summary stats
df_mult0.loc[:,['revenue','tev']].describe()
# lets fix that. What should we do? Pick some (not all) of the following
# set values that are nonsensical to NaN, keep the rest the same?
df_mult0.loc[:,'revenue'] = np.where(df_mult0['revenue']<=0, np.nan, df_mult0['revenue'])
df_mult0.loc[:,'tev'] = np.where(df_mult0['tev']<=0, np.nan, df_mult0['tev'])
# fill the NaN values with something?
#df_mult0['revenue'].fillna(value='NaN',inplace=True)
#df_mult0['tev'].fillna(value='NaN',inplace=True)
# drop NaN values?
df_mult0.dropna(axis=0, how='any', subset=['revenue','tev'], inplace=True)
# see if you solved the problem
df_mult0.loc[:,['revenue','tev']].describe()
# let's look at the dataframe, but only where the 'exchange' variable is NaN
df_mult0[df_mult0.exchange.isna()]
# this looks a lot like they are just international firms. We should keep these.
# Let's fill them with a value that may help us sort later.
# Also, later functions won't like NaN in our data.
df_mult0['exchange'].fillna(value='NonUS', inplace=True)
#all this does is replace the NaN above with the text NonUS for Non-US Exchanges.
#check how many NaN are left in the data frame
# what function did we use above to see the non-null counts?
df_mult0.info()
# working from the top, the next one with missing values is Market Cap. (depending on what you did exactly - it may be ok)
# if the firm is missing MarketCap, then that means we don't have stock price or shares outstanding.
# Either is a problem, so we have to drop these firms. How do we do that?
# drop NaN values
df_mult0.dropna(axis=0, how='any', subset=['marketcap'], inplace=True)
df_mult0.info()
# next we see total_debt.
# what does it mean if total_debt is missing? It likely means that the firm just has no debt.
# filter the Data Frame by only looking at firms where total_debt is NaN.
df_mult0.loc[:,'total_debt'] = df_mult0['total_debt'].replace(np.nan, 0)
df_mult0.info()
# Maybe we need to look by sector. Where are these firms with missing total_debt?
# Let's look at how many NaN we have in total_debt by sector.
# the first part is the same filter as above, then use the groupby() function on the variable 'sector'
df_mult0['total_debt'].groupby(df_mult0['sector'])
df_mult0.info()
# here is a nifty trick - combine two conditional statements: which firms have NaN total_debt and are NOT in the 'Financials' sector
# (note the parentheses around each conditional statement - this is important)
df_mult0.loc[(df_mult0.total_debt.isna()) & (df_mult0.sector != 'Financials')]
df_mult0.info()
# since we feel comfortalbe that these are normal companies but that they just have 0 total_debt, let's fill the NaN with 0
df_mult0['total_debt'].replace(np.nan, 0)
# make sure you set the correct second parameter to update the DataFrame.
df_mult0.info()
# here is another trick, though you may not need it.
# what if a firm has EBIT but not EBITDA? We want to keep the firm, so we fill the EBITDA field with the EBIT, assuming that DA are 0 instead of NaN
df_mult0['ebitda'].fillna(value=df_mult0['ebit'], inplace=True)
df_mult0.info()
You are now a lot of the way there. The goal is to do this kind of analysis all the way down for all of your variables, finding why there is any odd data - negatives, NaN, zeros, etc. You can use filters, look at the data, summary stats, etc.
# lets put all of our conditions into a single index object, then use that to filter our dataframe
# do an internet search for "pandas dataframe slice by multiple conditions" if you want more info on what I'm doing here
# each of these create a boolean series using specific criteria.
idx_margin = (df_mult0.ebit_margin > 10) & (df_mult0.ebit_margin < 20)
idx_roc = (df_mult0.roc > 10) & (df_mult0.roc < 20)
idx_growth = (df_mult0.gRev1yr > 0) & (df_mult0.gRev1yr < 20)
idx_beta = (df_mult0.beta2 > 0.8) & (df_mult0.beta2 < 1.2)
# these are all boolean conditions, so we just use AND to combine them
idx = idx_margin & idx_roc & idx_growth & idx_beta
# now we pass idx for the row portion, and the list of columns we want to see
df_mult0.loc[idx,['ebit_margin','roc','gRev1yr','beta2']].describe()
# now, compute the EV/EBITDA multiple. Or EV/EBIT. Or perhaps Price/Sales (MarketCap/Revenue).
# note: you may want to use np.where() if your denominator is zero. You'll see inf or NaN for your multiple in that case.
df_mult0['ev_ebitda'] = df_mult0['tev']/df_mult0['ebitda']
# check out your multiple
np.where(df_mult0['ebitda']==0,'NA', df_mult0['ev_ebitda'] )
df_mult0[idx].sort_values(by='ev_ebitda')
# want to do a histogram?
df_mult0[idx]['ev_ebitda'].hist()
# for an overvalued company
idx_margin = (df_mult0.ebit_margin > -5) & (df_mult0.ebit_margin < 10)
idx_roc = (df_mult0.roc > -10) & (df_mult0.roc < 10)
idx_growth = (df_mult0.gRev1yr < 0) & (df_mult0.gRev1yr < 20)
idx_beta = (df_mult0.beta2 > 0.8) & (df_mult0.beta2 < 1.2)
idx = idx_margin & idx_roc & idx_growth & idx_beta
df_mult0.loc[idx,['ebit_margin','roc','gRev1yr','beta2']].describe()
df_mult0['ev_ebitda'] = df_mult0['tev']/df_mult0['ebitda']
df_mult0[idx]['ev_ebitda'].describe()
df_mult0[idx].sort_values(by='cash')
df_mult0[idx].sort_values(by='roe')
df_mult0[idx].sort_values(by='ev_ebitda')
df_mult0[idx]['ev_ebitda'].hist(range=(-100,300))
# we believe that the Nokia Corporation(HLSE:NOKIA)and is an overvalued firm and would issue a SELL on it. Based on our analysis, we saw that Nokia was a firm with a low EV/EBITDA, negative ROE, negative 3-yr growth rate, and a low cash to CAPEX compared to their peers.
# Without the addition of capex innovation for their business segments, Nokia is trending downwards and does not have any catalysts right now outside of potential M&A given their large cash position.
# Additionally, we feel that Amtech Systems, Inc. (NasdaqGS:ASYS), Navistar International Corporation (NYSE:NAV), and Vidler Water Resources, Inc. (NasdaqGS:VWTR) would be an immediate sell given their EV/EBITA multiple and terrible 1 & 3yr growth outlook.
# Seems like we're on the right track with ASYS, their shares are down more than 40% recently and are getting downgraded due to recent earnings and forward looking guidance.
#For an undervalued company
idx_margin = (df_mult0.ebit_margin < 20) & (df_mult0.ebit_margin < 40)
idx_roc = (df_mult0.roc > 5) & (df_mult0.roc < 50)
idx_growth = (df_mult0.gRev1yr < 0) & (df_mult0.gRev1yr < 20)
idx_beta = (df_mult0.beta2 > 0.8) & (df_mult0.beta2 < 1.2)
idx = idx_margin & idx_roc & idx_growth & idx_beta
df_mult0.loc[idx,['ebit_margin','roc','gRev1yr','beta2']].describe()
df_mult0['ev_ebitda'] = df_mult0['tev']/df_mult0['ebitda']
df_mult0[idx]['ev_ebitda'].describe()
df_mult0[idx].sort_values(by='ev_ebitda')
df_mult0[idx].sort_values(by='cash')
df_mult0[idx].sort_values(by='roe')
df_mult0[idx]['ev_ebitda'].hist()
# we believe that the Starbucks Corporation (NasdaqGS:SBUX) is an undervalued firm and would issue a BUY on it. Based on our analysis, we saw that Starbucks was a firm with a higher EV/EBITDA, high ROE, positive 3-yr growth rate, and a high cash to CAPEX compared to their peers.
# It's very interesting to look at Starbucks because they have a very negative 1yr growth rate that might have come from COVID but looks to settle in the mid-term. We think that Starbucks would be a great buy that could show a positive return given their superior financials and large cash position.
# Additionally, they have enough liquidity to perform M&A deals in the market and have prioritized R&D as a core business segment.
# We double checked our analysis on Thomson Reuters Eikon and they're showing a short-term undervalued metric of Starbucks and shows that the median analyst price target is over 15% away.
# Also, we believe that Lawson Products, Inc. (NasdaqGS:LAWS) and VEON Ltd. (NasdaqGS:VEON) are undervalued and should issue a BUY on that as well. It shows a lower 1-yr growth rate than its peers but also has outperformed the market and has very realistic multiples given our EV / EBITDA calculations.
#To summarize:
#Overvalued Firms that we issue a Sell on are: Amtech Systems, Inc. (NasdaqGS:ASYS), Navistar International Corporation (NYSE:NAV), and Vidler Water Resources, Inc. (NasdaqGS:VWTR)
#Nokia is more speculative given further financial analysis but the firms above have very high multiples and can be considered overvalued.
#Undervalued firms that we issue a Buy on are: Lawson Products, Inc. (NasdaqGS:LAWS), Hollysys Automation Technologies Ltd. (NasdaqGS:HOLI), and VEON Ltd. (NasdaqGS:VEON)
#Starbucks is more speculative given further financial analysis but the firms above have very low multiples and can be considered undervalued.
# we added extra factors into our decisions for Nokia and Starbucks because we've never worked with this sort of analysis and wanted to try and utilize different factors. For the other firms above, we just used the EV/EBITDA multiples that we were instructed for.