Fall 2020 Courses

Foundations of Impactful Teamwork
Required Course for 1st Year MBA Students

Working effectively in and leading teams are essential competencies in modern organizations, both large and small. The purpose of this course is to lay a foundation of knowledge and skills that will enable you to differentiate yourself as an effective leader and member of impactful teams. The specific learning objectives for this course include:

  • Be able to launch and lead goal-directed project teams that meet or exceed stakeholders’ expectations for task performance, provide a positive working experience for team members, and enable team members to grow as a unit and as individuals.
  • Be able to diagnose common interpersonal challenges that arise in teams composed of diverse individuals who are working under pressure and relying heavily on virtual modes of collaboration.
  • Refine your awareness of your strengths and weaknesses as a leader and develop a plan for honing your leadership identity and interpersonal skills during your MBA program.
  • Augment your resourcefulness when working in a global virtual team.

Organizational Research Methods
Doctoral Course

The purpose of this course is to expose you to a range of methods for conducting research on organizations. We will do this through readings, class discussions and exercises, as well as through writing and reviewing one another’s work. Because this is a survey course, we will cover a range of topics and specific research methods. The objectives of the course are:

  • Introduce you to general concepts of methodological rigor and the core foundations of measurement.
  • Enhance your understanding of the suite of methods commonly used in organizational research.
  • Improve your skill in critically consuming research from a variety of methodological approaches.

Use R to Transcribe Zoom Audio files for use with zoomGroupStats

The zoomGroupStats functions that I’ve been building over the past few months have, to date, relied heavily on the transcription that is created automatically when a meeting is recorded to the Zoom Cloud. This is an excellent option if your account has access to Cloud Recording; however, it can be an obstacle if you want meeting leaders to record their own meetings (locally) and send you the file. In a recent project, for example, I had many meeting leaders who accidentally recorded their meetings locally, which left me without a transcript of the meeting.

This week I’ve started building a set of functions to take in an audio file from a Zoom meeting (or could also take in the video file, but that is unnecessary) and output the same transcript object that the processZoomTranscript function in zoomGroupStats produces. These functions rely on AWS Transcribe and S3. There are currently just two functions — one that launches a transcription job (since these are done asynchronously) and the second that parses the output of the transcription job.

Note that these functions currently use the default segmenting algorithm in AWS transcribe. From reviewing several transcriptions, it’s not very good (in my opinion). If your work requires utterance-level analysis (e.g., average utterance length), I would consider defining your own segmentation approach. The functions will output a simple text file transcript, so you could use that to do a custom segmentation.

############################################################
# transcribeZoomAudio Function
############################################################

# Zoom Audio File Processing, Function to launch transcription jobs
# This function starts an audio transcription job only == it does not output anything of use. However,
# it is useful for batch uploading audio files and starting transcription jobs for them.

# This can be done with a local file (uploads to a specified s3 bucket) or with a file that already
# exists in an s3 bucket

# example call:             transcribeZoomAudio(fileLocation="local", bucketName="my-transcription-bucket", filePath="mylocalfile.m4a", jobName="mylocalfile.m4a", languageCode="en-US")

# INPUT ARGUMENTS:
# fileLocation:             either "local" or "s3" - if local, then this function will upload the file to the specified bucket
# bucketName:               name of an existing s3 bucket that you are using for storing audio files to transcribe and finished transcriptions
# filePath:                 the path to the local file or to the s3 file (depending on whether it is "local" or "s3")
# jobName:                  the name of the transcription job for aws -- I set this to the same as the filename (without path) for convenience
# numSpeakers:              this helps AWS identify the speakers in the clip - specify how many speakers you expect
# languageCode:             the code for the language (e.g., en-US)

# OUTPUT:
# None

transcribeZoomAudio = function(fileLocation, bucketName, filePath, jobName, numSpeakers, languageCode) {
    require(paws)

    # First, if the file location is local, then upload it into the
    # designated s3 bucket
    if(fileLocation == "local") {
        localFilePath = filePath
        svc = s3()
        upload_file = file(localFilePath, "rb")
        upload_file_in = readBin(upload_file, "raw", n = file.size(localFilePath))
        svc$put_object(Body = upload_file_in, Bucket = bucketName, Key = jobName)
        filePath = paste("s3://", bucketName, "/",jobName, sep="")
        close(upload_file)
    }

    svc = transcribeservice()  
    svc$start_transcription_job(TranscriptionJobName = jobName, LanguageCode = languageCode, Media = list(MediaFileUri = filePath), OutputBucketName = bucketName, Settings = list(ShowSpeakerLabels=TRUE, MaxSpeakerLabels=numSpeakers))
}


############################################################
# processZoomAudio Function
############################################################

# Zoom Audio File Processing, process finished transcriptions
# This function parses the JSON transcription completed by AWS transcribe.
# The output is the same as the processZoomTranscript function.

# example call:             audio.out = processZoomAudio(bucketName = "my-transcription-bucket", jobName = "mylocalfile.m4a", localDir = "path-to-local-directory-for-output", speakerNames = c("Tom Smith", "Jamal Jones", "Jamika Jensen"), recordingStartDateTime = "2020-06-20 17:00:00", writeTranscript=TRUE)

# INPUT ARGUMENTS:
# bucketName:               name of the s3 bucket where the finished transcript is stored
# jobName:                  name of the transcription job (see above - i usually set this to the filename of the audio)
# localDir:                 a local directory where you can save the aws json file and also a plain text file of the transcribed text
# speakerNames:             a vector with the Zoom user names of the speakers, in the order in which they appear in the audio clip.
# recordingStartDateTime:   the date/time that the meeting recording started
# writeTranscript:          a boolean to indicate whether you want to output a plain text file of the transcript           

# OUTPUT:
# utterance_id:             an incremented numeric identifier for a marked speech utterance
# utterance_start_seconds   the number of seconds from the start of the recording (when it starts)
# utterance_start_time:     the timestamp for the start of the utterance
# utterance_end_seconds     the number of seconds from the start of the recording (when it ends)
# utterance_end_time:       the timestamp for the end of the utterance
# utterance_time_window:    the number of seconds that the utterance took
# user_name:                the name attached to the utterance
# utterance_message:        the text of the utterance
# utterance_language:       the language code for the transcript



processZoomAudio = function(bucketName, jobName, localDir, speakerNames=c(), recordingStartDateTime, writeTranscript) {
    require(paws)
    require(jsonlite)

    transcriptName = paste(jobName, "json", sep=".")
    svc = s3()
    transcript = svc$get_object(Bucket = bucketName, Key = transcriptName)
    # Write the binary component of the downloaded object to the local path
    writeBin(transcript$Body, con = paste(localDir, transcriptName, sep="/"))
    tr.json = fromJSON(paste(localDir, transcriptName, sep="/"))

    if(writeTranscript) {
        outTranscript = paste(localDir, "/", jobName, ".txt", sep="")
        write(tr.json$results$transcripts$transcript, outTranscript)
    }

    # This IDs the words as AWS broke out the different segments of speech
    for(i in 1:length(tr.json$results$speaker$segments$items)){

        res.line = tr.json$results$speaker$segments$items[[i]]
        res.line$segment_id = i
        if(i == 1) {
            res.out = res.line
        } else {
            res.out = rbind(res.out, res.line)
        }

    }

    segments = res.out 
    segment_cuts = tr.json$results$speaker$segments[,c("start_time", "speaker_label", "end_time")] 

    # Pull this apart to just get the word/punctuation with the most confidence
    # Not currently dealing with any of the alternatives that AWS could give
    for(i in 1:length(tr.json$results$items$alternatives)) {

        res.line = tr.json$results$items$alternatives[[i]]

        if(i == 1) {
            res.out = res.line
        } else {
            res.out = rbind(res.out, res.line)
        }

    }

    words = cbind(res.out, tr.json$results$items[,c("start_time", "end_time", "type")])
    words = words[words$type == "pronunciation", ]
    words_segments = merge(words, segments, by=c("start_time", "end_time"), all.x=T)

    words_segments$start_time = as.numeric(words_segments$start_time)
    words_segments$end_time = as.numeric(words_segments$end_time)

    words_segments = words_segments[order(words_segments$start_time), ]
    segment_ids = unique(words_segments$segment_id)
    i = 1


    segment_cuts$utterance_id = NA
    segment_cuts$utterance_message = NA
    for(i in 1:length(segment_ids)) {
        utterance_id = segment_ids[i]
        segment_cuts[i, "utterance_id"] = utterance_id     
        segment_cuts[i, "utterance_message"] = paste0(words_segments[words_segments$segment_id == utterance_id, "content"], collapse=" ")
    }  

    if(length(speakerNames) > 0) {
        user_names = data.frame(0:(length(speakerNames)-1), speakerNames, stringsAsFactors=F)
        names(user_names) = c("speaker_label", "user_name")
        user_names$speaker_label = paste("spk",user_names$speaker_label, sep="_")
        segment_cuts = merge(segment_cuts, user_names, by="speaker_label", all.x=T)
    }

    names(segment_cuts)[2:3] = c("utterance_start_seconds", "utterance_end_seconds")
    segment_cuts[, 2:3] = lapply(segment_cuts[, 2:3], function(x) as.numeric(x))
    segment_cuts = segment_cuts[order(segment_cuts$utterance_start_seconds), ]

    # Now turn these into actual datetime values
    recordingStartDateTime = as.POSIXct(recordingStartDateTime)
    segment_cuts$utterance_start_time = recordingStartDateTime + segment_cuts$utterance_start_seconds
    segment_cuts$utterance_end_time = recordingStartDateTime + segment_cuts$utterance_end_seconds

    # Create a time window (in seconds) for the utterances -- how long is each in seconds
    segment_cuts$utterance_time_window = as.numeric(difftime(segment_cuts$utterance_end_time, segment_cuts$utterance_start_time, units="secs"))

    # Prepare the output file
    res.out = segment_cuts[, c("utterance_id", "utterance_start_seconds", "utterance_start_time", "utterance_end_seconds", "utterance_end_time", "utterance_time_window", "user_name", "utterance_message")]

    # Mark as unidentified any user with a blank username
    res.out$user_name = ifelse(res.out$user_name == "" | is.na(res.out$user_name), "UNIDENTIFIED", res.out$user_name)      

    # Add the language code
    res.out$utterance_language = languageCode

    return(res.out)    

}

Meeting Measures: Feedback from Zoom

I created a website to give feedback to people on their virtual meetings. This website (http://www.meetingmeasures.com) relies on the code I’ve shared in past posts on how to quantify virtual meetings. The purpose of the site is to (a) unobtrusively capture people’s behavior in virtual meetings, (b) give people feedback on their presence and contributions in virtual meetings, and (c) suggest ways to improve their leadership and/or engagement in virtual meetings. There are currently options to incorporate survey data into the dashboard, as well.

This was a fun project to build. So far, I’ve administered > 100 meetings through the website. If you are interested in partnerships that involve the potential for research on virtual meeting behavior, please reach out.

Using R to Analyze Zoom Recordings

UPDATE: 2021-05-13: zoomGroupStats is now available as a package on CRAN.


# To use the stable release version of zoomGroupStats, use:
install.packages("zoomGroupStats")

# To use the development version, which will be regularly updated with new functionality, use:
library(devtools)
install_github("https://github.com/andrewpknight/zoomGroupStats")

You can stay up-to-date on the latest functionality on http://zoomgroupstats.org.

UPDATE: 2021-04-30: In response to prodding from several folks who have been using the functions, I have started to build these R functions for analyzing Zoom meetings as a package. I’m grateful for all of the feedback and suggestions for features and modifications to this project. Although things are still in flux, you can now access a package version of zoomGroupStats. The easiest way is to use the dev_tools package and install the current development version from my github repository. To do so, you can run:


library(devtools)
install_github("https://github.com/andrewpknight/zoomGroupStats", force=TRUE)
library(zoomGroupStats)

I have created a multi-part guide for using this package to conduct research using Zoom and analyze data from Zoom, which I will continue to extend and elaborate. To keep up-to-date on this work, please use the dedicated package website http://zoomgroupstats.org.

Please keep the feedback and comments coming!

UPDATE: 2021-02-12: I’ve updated several items in the package. I’m also going to moving the development and updates all over to github to ease version control and documentation. For now, here is a post that is from a recent live tutorial session I facilitated. Stay tuned!

UPDATE: 2020-07-27: The new file contains some alpha-stage functions for doing audio transcription and for conducting a windowed conversation analysis. I haven’t yet tested these functions extensively or commented the windowed conversation analysis. Once COVID teaching planning eases, I’ll get back in an update further.

UPDATE: 2020-04-14: I added a new function (textConversationAnalysis) that gives some very basic and descriptive conversation metrics from either the video transcript or the chat file.

You can always access the most recent version of the functions by including the statement source(“http://apknight.org/zoomGroupStats.R”) at the top of your code.

Alternatively, you could go to http://apknight.org/zoomGroupStats.R and copy/paste the code into your editor.

ORIGINAL POST FOLLOWS:

In response to the shift to so many online meetings, I created a set of R functions to help do research using web-based meetings. In brief, these functions use the output of recorded sessions (e.g., video feed, transcript file, chat file) to do things like sentiment analysis, face analysis, and emotional expression analysis. In the coming week, I will extend these to do basic conversation analysis (e.g., who speaks/chats most, turntaking).

I went overboard in commenting the code so that hopefully others can use them. But, if you’re still having trouble getting them to work, please don’t hesitate to reach out to me.

You can directly access the functions here:
http://apknight.org/zoomGroupStats.R

After reviewing this, you could call these functions using the following statement in R: source(“http://apknight.org/zoomGroupStats.R”)

Using R for Face Detection through AWS Rekognition

Today I experimented a little with the Rekognition service that AWS offers. I started out by experimenting with doing a Python version of this project, following this K-pop Idol Identifier with Rekognition post. It was pretty easy to setup; however, I tend to use R more than Python for data analysis and manipulation.

I found the excellent paws package, which is available through CRAN. The documentation for the paws package is very good, organized in an attractive github site here.

To start, I just duplicated the Python project in R, which was fairly straightforward. Then, I expanded on it a bit to annotate a photo with information about the emotional expressions being displayed by any subjects. The annotated image above shows what the script outputs if it is given a photo of my kids. And, here’s the commented code that walks through what I did.

########################
# Setup the environment with libraries and the key service
########################

# Use the paws library for easy access to aws
# Note: You will need to authenticate. For this project, I have my credentials in an
# AWS configuration; so, paws looks there for them.
# paws provides good information on how to do this:
# https://github.com/paws-r/paws/blob/master/docs/credentials.md
library(paws)

# Use the magick library for image functions.
library(magick)

# This application is going to use the rekognition service from AWS. The paws documentation is here:
# # https://paws-r.github.io/docs/rekognition/
svc <- rekognition()

########################
# First, create a new collection that you will use to store the set of identified faces. This will be
# the library of faces that you use for determining someone's identity
########################

# I am going to create a collection for a set of family photos
svc$create_collection(CollectionId = "family-r")

# I stored a set of faces of my kids in a single directory on my Desktop. Inside
# this directory are multiple photos of each person, with the filename set as personname_##.png. This
# means that there are several photos per kid, which should help with classification.

# Get the list of files
path = "~/Desktop/family"
file.names = dir(path, full.names=F)

# Loop through the files in the specified folder, add and index them in the collection
for(f in file.names) {
    imgFile = paste(path,f,sep="/")
    # This gets the name of the kid, which is embedded in the filename and separated from the number with an underscore
    imgName = unlist(strsplit(f,split="_"))[[1]]
    # Add the photos and the name to the collection that I created
    svc$index_faces(CollectionId="family-r", Image=list(Bytes=imgFile), ExternalImageId=imgName, DetectionAttributes=list())
}

########################
# Second, take a single photo that has multiple kids in it. Label each kid with his name and the
# emotions that are expressed in the photo.
########################

# Get information about a group photo
grp.photo = "~/Desktop/all_three_small.png"

# Read the photo using magick
img = image_read(grp.photo)

# Get basic informatino about the photo that will be useful for annotating
inf = image_info(img)

# Detect the faces in the image and pull all attributes associated with faces
o = svc$detect_faces(Image=list(Bytes=grp.photo), Attributes="ALL")

# Just get the face details
all_faces = o$FaceDetails
length(all_faces)

# Loop through the faces, one by one. For each face, draw a rectangle around it, add the kid's name, and emotions

# Duplicate the original image to have something to annotate and output
new.img = img

for(face in all_faces) {

    # Prepare a label that collapses across the emotions data provided by rekognition. Give the type of
    # emotion and the confidence that AWS has in its expression.
    emo.label = ""
    for(emo in face$Emotions) {
        emo.label = paste(emo.label,emo$Type, " = ", round(emo$Confidence, 2), "\n", sep="")
    }

    # Identify the coordinates of the face. Note that AWS returns percentage values of the total image size. This is
    # why the image info object above is needed
    box = face$BoundingBox
    image_width=inf$width
    image_height=inf$height
    x1 = box$Left*image_width
    y1 = box$Top*image_height
    x2 = x1 + box$Width*image_width
    y2 = y1 + box$Height*image_height  

    # Create a subset image in memory that is just cropped around the focal face
    img.crop = image_crop(img, paste(box$Width*image_width,"x",box$Height*image_height,"+",x1,"+",y1, sep=""))
    img.crop = image_write(img.crop, path = NULL, format = "png")

    # Search in a specified collection to see if we can label the identity of the face is in this crop
    o = svc$search_faces_by_image(CollectionId="family-r",Image=list(Bytes=img.crop), FaceMatchThreshold=70)

    # Create a graphics device version of the larger photo that we can annotate
    new.img = image_draw(new.img)

    # If the face matches something in the collection, then add the name to the image
    if(length(o$FaceMatches) > 0) {
        faceName = o$FaceMatches[[1]]$Face$ExternalImageId
        faceConfidence = round(o$FaceMatches[[1]]$Face$Confidence,3)
        print(paste("Detected: ",faceName, sep=""))
        # Annotate with the name of the person
        text(x=x1+(box$Width*image_width)/2, y=y1,faceName, adj=0.5, cex=3, col="green")
    }

    # Draw a rectangle around the face
    rect(x1,y1,x2,y2, border="red", lty="dashed", lwd=5)   

    # Annotate the photo with the emotions information
    text(x=x1+(box$Width*image_width)/2, y=y1+50,emo.label, pos=1, cex=1.5, col="red")     

    dev.off()
}

# Write the image out to file
image_write(new.img, path="~/Desktop/annotated_image.png", format="png")

Start-Up Teams: A Multidimensional Conceptualization, Integrative Review of Past Research, and Future Research Agenda

Knight, A. P., Greer, L. L., & de Jong, B. (2020). Start-up teams: A multidimensional conceptualization, integrative review of past research, and future research agenda. Academy of Management Annals, 14, 231-266.

Abstract. Academic interest in start-up teams has grown dramatically over the past 40 years, with researchers from a wide variety of disciplines actively studying the topic. Although this widespread interest is encouraging, a review of the literature reveals a lack of consensus in how researchers conceptualize and operationally define start-up teams. A lack of consensus on the core phenomenon—a foundational part of a strong paradigm—has stifled the systematic advancement of knowledge about start-up teams, which has downstream implications for the viability of this field of research. To advance the development of a stronger paradigm, we present a multidimensional conceptualization of start-up teams that is derived from points of consensus in existing definitions. Our multidimensional conceptualization accounts for the fact that, although all are under the umbrella of the concept of “start-up team,” start-up teams vary in a set of key ingredients—ownership of equity, autonomy of strategic decision-making, and entitativity. This conceptualization serves as a framework for reviewing and beginning to integrate past research on start-up teams. It also serves as a framework for guiding and informing an integrated program of future research on start-up teams. By introducing a multidimensional conceptualization of start-up teams, we highlight the value of considering the defining ingredients of start-up teams for furthering a stronger paradigm.

On the relation between felt trust and actual trust: Examining pathways to and implications of leader trust meta-accuracy

Campagna, R. L., Dirks, K. T., Knight, A. P., Crossley, C., & Robinson, S. L. (2020). On the relation between felt trust and actual trust: Examining pathways to and implications of leader trust meta-accuracy. Journal of Applied Psychology, 105, 994-1012.

Abstract. Research has long emphasized that being trusted is a central concern for leaders (Dirks & Ferrin, 2002), but an interesting and important question left unexplored is whether leaders feel trusted by each employee, and whether their felt trust is accurate. Across two field studies, we examined the factors that shape the accuracy of leaders’ felt trust—or, their trust meta-accuracy—and the implications of trust meta- accuracy for the degree of relationship conflict between leaders and their employees. By integrating research on trust and interpersonal perception, we developed and tested hypotheses based on two theoretical mechanisms—an external signaling mechanism and an internal presumed reciprocity mechanism—that theory suggests shape leaders’ trust meta-accuracy. In contrast to the existing literature on felt trust, our results reveal that leader trust meta-accuracy is shaped by an internal mechanism and the presumed reciprocity of trust relationships. We further find that whether trust meta-accuracy is associated with positive relational outcomes for leaders depends upon the level of an employee’s actual trust in the leader. Our research contributes to burgeoning interest in felt trust by elucidating the mechanisms underlying trust meta-accuracy and suggesting practical directions for leaders who seek to accurately understand how much their employees trust them.

Spring 2020 Courses

People Metrics
Open to BSBA, MBA, and Specialized Masters students

Metrics are at the core of people analytics. The purpose of this course is to introduce you to the foundations of assessing behavior in organizations using novel measurement approaches and large datasets. Through classroom discussions and real-world applications, this course will enable you to add value to organizations through the development, use, and interpretation of innovative people metrics. Specifically, after taking this course, you will be able to:

  • Develop a clear and logical conceptual measurement model. A conceptual measurement model is the foundation of creating novel and useful new approaches for assessing intrapersonal characteristics (e.g., personality) and interpersonal behavior (e.g., knowledge sharing, teamwork).
  • Identify novel sources of data for innovative people metrics. Organizations are awash in the traces of individual behavior and social interactions. Decoding how data that already exist in an organization can be used to understand behavior is an essential skill for adding value in the field of people analytics.
  • Apply a rigorous process for validating new people metrics. Developing a measurement model and finding sources of data are necessary, but insufficient for adding value through people metrics. New measures must be validated.

Fall 2019 Courses

Leadership Development
2nd Year Full-Time MBA OB Core Course

This course builds upon the material from the 1st Year OB Core (OB 5620, Foundations for Leadership Effectiveness) and, importantly, from your time so far at Olin and during your summer work experiences. The focus of the course is on the attributes, behaviors, and tendencies of effective leadership. There are two primary objectives:

  • Gain new insights into your own beliefs and expectations regarding what constitutes effective leadership in organizations. You will accomplish this through a mixture of classroom discussion, case analysis, and self-assessment.
  • Learn about your own strengths and weaknesses in leading others. You will accomplish this in the classroom through controlled experiential exercises, which will be the basis for feedback from your peers. You will also reflect in depth on your strengths using feedback provided by people you have encountered in your life and career through a structured exercise.

Summer 2019 Courses

Foundations of Impactful Teamwork
Required Course for 1st Year MBA Students

Working effectively in and leading teams are essential competencies in modern organizations, both large and small. The purpose of this course is to lay a foundation of knowledge and skills that will enable you to differentiate yourself as an effective leader and member of impactful teams. The specific learning objectives for this course include:

  • Be able to skillfully launch and lead goal-directed project teams that meet or exceed stakeholders’ expectations for task performance, provide a positive working experience for team members, and enable team members to grow as a unit and as individuals.
  • Be able to diagnose common interpersonal challenges that arise in teams composed of diverse individuals who are working under pressure in unfamiliar environments; and, apply evidence-based practices for leading teams through these challenges.
  • Refine your awareness of your personal strengths and weaknesses as a leader and develop a plan for honing your leadership identity and interpersonal skills during your MBA program.
  • Augment your resourcefulness when navigating diverse local environments and unfamiliar cultures and societies.